Merge "Add a test api for setting launch cookies" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index c43aa75..f5bf437 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -13,71 +13,143 @@
 // limitations under the License.
 
 aconfig_srcjars = [
-    ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+    // !!! KEEP THIS LIST ALPHABETICAL !!!
+    ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}",
+    ":android.adaptiveauth.flags-aconfig-java{.generated_srcjars}",
+    ":android.app.flags-aconfig-java{.generated_srcjars}",
     ":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
+    ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+    ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
+    ":android.chre.flags-aconfig-java{.generated_srcjars}",
     ":android.companion.flags-aconfig-java{.generated_srcjars}",
+    ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
+    ":android.content.flags-aconfig-java{.generated_srcjars}",
     ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
     ":android.content.res.flags-aconfig-java{.generated_srcjars}",
+    ":android.credentials.flags-aconfig-java{.generated_srcjars}",
+    ":android.database.sqlite-aconfig-java{.generated_srcjars}",
+    ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
+    ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
     ":android.location.flags-aconfig-java{.generated_srcjars}",
+    ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
+    ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
     ":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
     ":android.nfc.flags-aconfig-java{.generated_srcjars}",
     ":android.os.flags-aconfig-java{.generated_srcjars}",
     ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
+    ":android.permission.flags-aconfig-java{.generated_srcjars}",
+    ":android.provider.flags-aconfig-java{.generated_srcjars}",
     ":android.security.flags-aconfig-java{.generated_srcjars}",
     ":android.server.app.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
     ":android.service.chooser.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
     ":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
     ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
-    ":android.view.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+    ":android.speech.flags-aconfig-java{.generated_srcjars}",
+    ":android.tracing.flags-aconfig-java{.generated_srcjars}",
     ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
+    ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
+    ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
+    ":android.view.flags-aconfig-java{.generated_srcjars}",
+    ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
+    ":android.webkit.flags-aconfig-java{.generated_srcjars}",
+    ":android.widget.flags-aconfig-java{.generated_srcjars}",
     ":audio-framework-aconfig",
     ":camera_platform_flags_core_java_lib{.generated_srcjars}",
-    ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
-    ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
     ":com.android.hardware.input-aconfig-java{.generated_srcjars}",
     ":com.android.input.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
+    ":com.android.net.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
     ":com.android.text.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
+    ":device_policy_aconfig_flags_lib{.generated_srcjars}",
+    ":display_flags_lib{.generated_srcjars}",
     ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}",
+    ":framework_graphics_flags_java_lib{.generated_srcjars}",
+    ":hwui_flags_java_lib{.generated_srcjars}",
+    ":power_flags_lib{.generated_srcjars}",
+    ":sdk_sandbox_flags_lib{.generated_srcjars}",
+    ":surfaceflinger_flags_java_lib{.generated_srcjars}",
     ":telecom_flags_core_java_lib{.generated_srcjars}",
     ":telephony_flags_core_java_lib{.generated_srcjars}",
-    ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
-    ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
-    ":android.widget.flags-aconfig-java{.generated_srcjars}",
-    ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
-    ":sdk_sandbox_flags_lib{.generated_srcjars}",
-    ":android.permission.flags-aconfig-java{.generated_srcjars}",
-    ":android.database.sqlite-aconfig-java{.generated_srcjars}",
-    ":hwui_flags_java_lib{.generated_srcjars}",
-    ":framework_graphics_flags_java_lib{.generated_srcjars}",
-    ":display_flags_lib{.generated_srcjars}",
-    ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
-    ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
-    ":android.app.flags-aconfig-java{.generated_srcjars}",
-    ":android.credentials.flags-aconfig-java{.generated_srcjars}",
-    ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
-    ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
-    ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
-    ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
-    ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
-    ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
-    ":com.android.net.flags-aconfig-java{.generated_srcjars}",
-    ":device_policy_aconfig_flags_lib{.generated_srcjars}",
-    ":surfaceflinger_flags_java_lib{.generated_srcjars}",
-    ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
-    ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
-    ":android.tracing.flags-aconfig-java{.generated_srcjars}",
-    ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
-    ":android.webkit.flags-aconfig-java{.generated_srcjars}",
-    ":android.provider.flags-aconfig-java{.generated_srcjars}",
-    ":android.chre.flags-aconfig-java{.generated_srcjars}",
-    ":android.speech.flags-aconfig-java{.generated_srcjars}",
-    ":power_flags_lib{.generated_srcjars}",
-    ":android.content.flags-aconfig-java{.generated_srcjars}",
-    ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}",
+    // !!! KEEP THIS LIST ALPHABETICAL !!!
 ]
 
+stubs_defaults {
+    name: "framework-minus-apex-aconfig-declarations",
+    aconfig_declarations: [
+        "android.app.flags-aconfig",
+        "android.app.smartspace.flags-aconfig",
+        "android.app.usage.flags-aconfig",
+        "android.appwidget.flags-aconfig",
+        "android.companion.flags-aconfig",
+        "android.companion.virtual.flags-aconfig",
+        "android.content.pm.flags-aconfig",
+        "android.content.res.flags-aconfig",
+        "android.credentials.flags-aconfig",
+        "android.database.sqlite-aconfig",
+        "android.hardware.biometrics.flags-aconfig",
+        "android.hardware.flags-aconfig",
+        "android.hardware.radio.flags-aconfig",
+        "android.hardware.usb.flags-aconfig",
+        "android.location.flags-aconfig",
+        "android.media.audio-aconfig",
+        "android.media.audiopolicy-aconfig",
+        "android.media.midi-aconfig",
+        "android.media.tv.flags-aconfig",
+        "android.multiuser.flags-aconfig",
+        "android.net.vcn.flags-aconfig",
+        "android.nfc.flags-aconfig",
+        "android.os.flags-aconfig",
+        "android.os.vibrator.flags-aconfig",
+        "android.permission.flags-aconfig",
+        "android.provider.flags-aconfig",
+        "android.security.flags-aconfig",
+        "android.server.app.flags-aconfig",
+        "android.service.autofill.flags-aconfig",
+        "android.service.chooser.flags-aconfig",
+        "android.service.controls.flags-aconfig",
+        "android.service.dreams.flags-aconfig",
+        "android.service.notification.flags-aconfig",
+        "android.service.voice.flags-aconfig",
+        "android.speech.flags-aconfig",
+        "android.tracing.flags-aconfig",
+        "android.view.accessibility.flags-aconfig",
+        "android.view.contentcapture.flags-aconfig",
+        "android.view.contentprotection.flags-aconfig",
+        "android.view.flags-aconfig",
+        "android.view.inputmethod.flags-aconfig",
+        "android.webkit.flags-aconfig",
+        "android.widget.flags-aconfig",
+        "camera_platform_flags",
+        "chre_flags",
+        "com.android.hardware.input.input-aconfig",
+        "com.android.input.flags-aconfig",
+        "com.android.media.flags.bettertogether-aconfig",
+        "com.android.net.flags-aconfig",
+        "com.android.server.flags.services-aconfig",
+        "com.android.text.flags-aconfig",
+        "com.android.window.flags.window-aconfig",
+        "device_policy_aconfig_flags",
+        "display_flags",
+        "fold_lock_setting_flags",
+        "framework-jobscheduler-job.flags-aconfig",
+        "framework_graphics_flags",
+        "hwui_flags",
+        "power_flags",
+        "sdk_sandbox_flags",
+        "surfaceflinger_flags",
+        "telecom_flags",
+        "telephony_flags",
+    ],
+}
+
 filegroup {
     name: "framework-minus-apex-aconfig-srcjars",
     srcs: aconfig_srcjars,
@@ -962,3 +1034,16 @@
     aconfig_declarations: "android.content.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Adaptive Auth
+aconfig_declarations {
+    name: "android.adaptiveauth.flags-aconfig",
+    package: "android.adaptiveauth",
+    srcs: ["core/java/android/adaptiveauth/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.adaptiveauth.flags-aconfig-java",
+    aconfig_declarations: "android.adaptiveauth.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 485f1be..9c56733 100644
--- a/Android.bp
+++ b/Android.bp
@@ -216,7 +216,6 @@
         "apex_aidl_interface-java",
         "packagemanager_aidl-java",
         "framework-protos",
-        "libtombstone_proto_java",
         "updatable-driver-protos",
         "ota_metadata_proto_java",
         "android.hidl.base-V1.0-java",
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index d03bbd2..e7adf20 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -34,6 +34,7 @@
         ":ipconnectivity-proto-src",
         ":libstats_atom_enum_protos",
         ":libstats_atom_message_protos",
+        ":libtombstone_proto-src",
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
     ],
diff --git a/Ravenwood.bp b/Ravenwood.bp
index d13c4d7..0877bce 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -97,6 +97,7 @@
         "framework-minus-apex.ravenwood",
         "hoststubgen-helper-runtime.ravenwood",
         "hoststubgen-helper-framework-runtime.ravenwood",
+        "core-libart-for-host",
         "all-updatable-modules-system-stubs",
         "junit",
         "truth",
diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS
deleted file mode 100644
index a7ecb4d..0000000
--- a/STABILITY_OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
[email protected]
-
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 2c9af67..44afbe6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -951,7 +951,7 @@
         @VisibleForTesting
         static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
         @VisibleForTesting
-        static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS;
+        static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS;
         private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
         @VisibleForTesting
         final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
diff --git a/api/Android.bp b/api/Android.bp
index 1686943..b3b18b6 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -60,8 +60,40 @@
 metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
 metalava_cmd += " --quiet "
 
+soong_config_module_type {
+    name: "enable_crashrecovery_module",
+    module_type: "combined_apis_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: ["release_crashrecovery_module"],
+    properties: [
+        "bootclasspath",
+        "system_server_classpath",
+    ],
+}
+
+soong_config_bool_variable {
+    name: "release_crashrecovery_module",
+}
+
+enable_crashrecovery_module {
+    name: "crashrecovery_module_defaults",
+    soong_config_variables: {
+        release_crashrecovery_module: {
+            bootclasspath: [
+                "framework-crashrecovery",
+            ],
+            system_server_classpath: [
+                "service-crashrecovery",
+            ],
+        },
+    },
+}
+
 combined_apis {
     name: "frameworks-base-api",
+    defaults: [
+        "crashrecovery_module_defaults",
+    ],
     bootclasspath: [
         "android.net.ipsec.ike",
         "art.module.public.api",
@@ -269,6 +301,7 @@
 // classpath (or sources) somehow.
 stubs_defaults {
     name: "android-non-updatable-stubs-defaults",
+    defaults: ["framework-minus-apex-aconfig-declarations"],
     srcs: [":android-non-updatable-stub-sources"],
     sdk_version: "none",
     system_modules: "none",
diff --git a/boot/Android.bp b/boot/Android.bp
index c4a1139b7..228d060 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -30,6 +30,7 @@
     bool_variables: [
         "car_bootclasspath_fragment",
         "nfc_apex_bootclasspath_fragment",
+        "release_crashrecovery_module",
     ],
     properties: [
         "fragments",
@@ -165,6 +166,15 @@
                 },
             ],
         },
+        release_crashrecovery_module: {
+            fragments: [
+                // only used when crashrecovery is enabled
+                {
+                    apex: "com.android.crashrecovery",
+                    module: "com.android.crashrecovery-bootclasspath-fragment",
+                },
+            ],
+        },
     },
 
     // Additional information needed by hidden api processing.
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 16bb896..55ea15d 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -468,9 +468,9 @@
                                        entry.name().c_str());
         const auto& res_value = entry.res_value();
         result.pairs.emplace_back(OverlayData::Value{
-            name, TargetValueWithConfig{.config = entry.configuration(), .value = TargetValue{
+            name, TargetValueWithConfig{.value = TargetValue{
                     .data_type = static_cast<uint8_t>(res_value.data_type()),
-                    .data_value = res_value.data_value()}}});
+                    .data_value = res_value.data_value()}, .config = entry.configuration()}});
       }
     }
   }
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 7869fbd..3c0e118 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -227,9 +227,9 @@
     } else {
       overlay_data.pairs.emplace_back(
           OverlayData::Value{*target_resource, TargetValueWithConfig{
-              .config = std::string(),
               .value = TargetValue{.data_type = overlay_resource->dataType,
-                                   .data_value = overlay_resource->data}}});
+                                   .data_value = overlay_resource->data},
+              .config = std::string()}});
     }
   }
 
@@ -268,10 +268,11 @@
   std::unique_ptr<AssetManager2> am;
   ZipAssetsProvider* zip_assets;
 
-  static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip) {
+  static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip,
+                                     package_property_t flags) {
     ResState state;
     state.zip_assets = zip.get();
-    if ((state.apk_assets = ApkAssets::Load(std::move(zip))) == nullptr) {
+    if ((state.apk_assets = ApkAssets::Load(std::move(zip), flags)) == nullptr) {
       return Error("failed to load apk asset");
     }
 
@@ -284,7 +285,7 @@
     }
 
     state.am = std::make_unique<AssetManager2>();
-    if (!state.am->SetApkAssets({state.apk_assets})) {
+    if (!state.am->SetApkAssets({state.apk_assets}, false)) {
       return Error("failed to create asset manager");
     }
 
@@ -343,8 +344,8 @@
     return state;
   }
 
-  auto state =
-      ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)));
+  auto state = ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)),
+                                    PROPERTY_OPTIMIZE_NAME_LOOKUPS);
   if (!state) {
     return state.GetError();
   }
diff --git a/core/api/current.txt b/core/api/current.txt
index 617cd3f..40dcc42 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9379,15 +9379,15 @@
     method public int describeContents();
     method public long getBeginTimeMillis();
     method public long getEndTimeMillis();
-    method @NonNull public java.util.Set<java.lang.Integer> getEventTypes();
+    method @NonNull public int[] getEventTypes();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.UsageEventsQuery> CREATOR;
   }
 
   public static final class UsageEventsQuery.Builder {
     ctor public UsageEventsQuery.Builder(long, long);
-    method @NonNull public android.app.usage.UsageEventsQuery.Builder addEventTypes(@NonNull int...);
     method @NonNull public android.app.usage.UsageEventsQuery build();
+    method @NonNull public android.app.usage.UsageEventsQuery.Builder setEventTypes(@NonNull int...);
   }
 
   public final class UsageStats implements android.os.Parcelable {
@@ -9414,7 +9414,7 @@
     method public java.util.List<android.app.usage.ConfigurationStats> queryConfigurations(int, long, long);
     method public java.util.List<android.app.usage.EventStats> queryEventStats(int, long, long);
     method public android.app.usage.UsageEvents queryEvents(long, long);
-    method @FlaggedApi("android.app.usage.filter_based_event_query_api") @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
+    method @FlaggedApi("android.app.usage.filter_based_event_query_api") @Nullable @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
     method public android.app.usage.UsageEvents queryEventsForSelf(long, long);
     method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
     field @FlaggedApi("android.app.usage.user_interaction_type_api") public static final String EXTRA_EVENT_ACTION = "android.app.usage.extra.EVENT_ACTION";
@@ -12445,7 +12445,7 @@
     method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
     method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler);
     method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender);
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender);
@@ -12872,7 +12872,6 @@
     field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
     field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
     field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_ARCHIVE = 16; // 0x10
-    field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_SHOW_DIALOG = 32; // 0x20
     field public static final int DONT_KILL_APP = 1; // 0x1
     field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
     field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT";
@@ -13675,11 +13674,8 @@
   @FlaggedApi("android.content.res.font_scale_converter_public") public interface FontScaleConverter {
     method public float convertDpToSp(float);
     method public float convertSpToDp(float);
-  }
-
-  @FlaggedApi("android.content.res.font_scale_converter_public") public class FontScaleConverterFactory {
-    method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
-    method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread public static boolean isNonLinearFontScalingActive(float);
+    method @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
+    method @AnyThread public static boolean isNonLinearFontScalingActive(float);
   }
 
   public class ObbInfo implements android.os.Parcelable {
@@ -16399,6 +16395,8 @@
     field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
     field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
     field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
+    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
+    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
     field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
   }
 
@@ -18682,6 +18680,8 @@
     method @Nullable public int getAllowedAuthenticators();
     method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
     method @Nullable public CharSequence getDescription();
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap();
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes();
     method @Nullable public CharSequence getNegativeButtonText();
     method @Nullable public CharSequence getSubtitle();
     method @NonNull public CharSequence getTitle();
@@ -18731,6 +18731,8 @@
     method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
     method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int);
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@NonNull android.graphics.Bitmap);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -24304,7 +24306,7 @@
     method public void release();
     method public void selectRoute(@NonNull android.media.MediaRoute2Info);
     method public void setVolume(int);
-    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferRequestedBySelf();
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferInitiatedBySelf();
   }
 
   public abstract static class MediaRouter2.TransferCallback {
@@ -31850,6 +31852,7 @@
     method public long computeChargeTimeRemaining();
     method public int getIntProperty(int);
     method public long getLongProperty(int);
+    method @FlaggedApi("android.os.battery_part_status_api") @Nullable public String getStringProperty(int);
     method public boolean isCharging();
     field public static final String ACTION_CHARGING = "android.os.action.CHARGING";
     field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
@@ -51416,7 +51419,7 @@
     method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
     method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
     method @NonNull public android.view.SurfaceControl.Transaction setDataSpace(@NonNull android.view.SurfaceControl, int);
-    method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setDesiredPresentTime(long);
+    method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setDesiredPresentTimeNanos(long);
     method @NonNull public android.view.SurfaceControl.Transaction setExtendedRangeBrightness(@NonNull android.view.SurfaceControl, float, float);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
@@ -51437,7 +51440,7 @@
   }
 
   @FlaggedApi("com.android.window.flags.sdk_desired_present_time") public static final class SurfaceControl.TransactionStats {
-    method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") public long getLatchTime();
+    method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") public long getLatchTimeNanos();
     method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.hardware.SyncFence getPresentFence();
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2596f9c..b6d26df 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12,6 +12,7 @@
     field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
     field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER";
     field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACCESS_LAST_KNOWN_CELL_ID = "android.permission.ACCESS_LAST_KNOWN_CELL_ID";
     field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
     field public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION";
     field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
@@ -295,6 +296,7 @@
     field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
     field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS";
     field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
+    field @FlaggedApi("android.app.system_terms_of_address_enabled") public static final String READ_SYSTEM_GRAMMATICAL_GENDER = "android.permission.READ_SYSTEM_GRAMMATICAL_GENDER";
     field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO";
     field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
     field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
@@ -420,6 +422,7 @@
 
   public static final class R.attr {
     field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
+    field @FlaggedApi("android.content.res.manifest_flagging") public static final int featureFlag;
     field public static final int gameSessionService = 16844373; // 0x1010655
     field public static final int hotwordDetectionService = 16844326; // 0x1010626
     field @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public static final int isVirtualDeviceOnly;
@@ -553,6 +556,7 @@
 
   public class ActivityManager {
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int);
+    method @FlaggedApi("android.app.uid_importance_listener_for_uids") @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(@NonNull android.app.ActivityManager.OnUidImportanceListener, int, @Nullable int[]);
     method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String);
     method @FlaggedApi("android.app.get_binding_uid_importance") @RequiresPermission(android.Manifest.permission.GET_BINDING_UID_IMPORTANCE) public int getBindingUidImportance(int);
     method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL"}) public static int getCurrentUser();
@@ -1616,6 +1620,7 @@
     field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4
     field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2
     field public static final int LEVEL_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("android.app.ambient_heart_rate") public static final int RATE_PER_MINUTE_UNKNOWN = -1; // 0xffffffff
   }
 
   public static final class AmbientContextEvent.Builder {
@@ -3196,6 +3201,7 @@
 
   public final class VirtualDeviceManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams);
+    method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @Nullable public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String);
     field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2
     field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1
     field public static final int LAUNCH_SUCCESS = 0; // 0x0
@@ -9984,6 +9990,7 @@
 
   @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
     ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -9994,6 +10001,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public String getOffHostSecureElement();
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
@@ -10006,6 +10014,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void removePollingLoopFilter(@NonNull String);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresScreenOn();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
@@ -10044,12 +10053,17 @@
     field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9
     field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8
     field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7
+    field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_PART_STATUS = 12; // 0xc
+    field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11; // 0xb
     field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3
     field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2
     field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4
     field public static final int CHARGING_POLICY_DEFAULT = 1; // 0x1
     field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
     field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
+    field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_ORIGINAL = 1; // 0x1
+    field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_REPLACED = 2; // 0x2
+    field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_UNSUPPORTED = 0; // 0x0
   }
 
   public final class BatterySaverPolicyConfig implements android.os.Parcelable {
@@ -14686,6 +14700,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, "com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"}) public android.telephony.CellIdentity getLastKnownCellIdentity();
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
     method public int getMaxNumberOfSimultaneouslyActiveSims();
     method public static long getMaxNumberVerificationTimeoutMillis();
@@ -14789,6 +14804,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>);
     method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPin(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPuk(@NonNull String, @NonNull String);
@@ -14928,6 +14944,11 @@
     field public static final int ERROR_UNKNOWN = 0; // 0x0
   }
 
+  @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public class TelephonyRegistryManager {
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
+    method public void notifyOutgoingEmergencyCall(int, int, @NonNull android.telephony.emergency.EmergencyNumber);
+  }
+
   public final class ThermalMitigationRequest implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.telephony.DataThrottlingRequest getDataThrottlingRequest();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 38e8ec5..a866a34 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -488,10 +488,15 @@
   }
 
   public class UiModeManager {
+    method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getAttentionModeThemeOverlay();
     method public boolean isNightModeLocked();
     method public boolean isUiModeLocked();
     method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int);
     method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int);
+    field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002; // 0x3ea
+    field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001; // 0x3e9
+    field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000; // 0x3e8
+    field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1; // 0xffffffff
     field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff
     field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1
     field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
@@ -3249,7 +3254,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
     method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
-    method @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
     field public static final int HAL_SERVICE_DATA = 1; // 0x1
     field public static final int HAL_SERVICE_IMS = 7; // 0x7
     field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS
new file mode 100644
index 0000000..0218a78
--- /dev/null
+++ b/core/java/android/adaptiveauth/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file
diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig
new file mode 100644
index 0000000..39e46bb
--- /dev/null
+++ b/core/java/android/adaptiveauth/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.adaptiveauth"
+
+flag {
+  name: "report_biometric_auth_attempts"
+  namespace: "biometrics"
+  description: "Control the usage of the biometric auth signal in adaptive auth"
+  bug: "285053096"
+}
\ No newline at end of file
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1fd49ef..5318bb7 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4402,7 +4402,7 @@
     }
 
     /**
-     * Start monitoring changes to the importance of uids running in the system.
+     * Start monitoring changes to the importance of all uids running in the system.
      * @param listener The listener callback that will receive change reports.
      * @param importanceCutpoint The level of importance in which the caller is interested
      * in differences.  For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
@@ -4422,17 +4422,48 @@
     @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
     public void addOnUidImportanceListener(OnUidImportanceListener listener,
             @RunningAppProcessInfo.Importance int importanceCutpoint) {
-        synchronized (this) {
+        addOnUidImportanceListenerInternal(listener, importanceCutpoint, null /* uids */);
+    }
+
+    /**
+     * Start monitoring changes to the importance of given uids running in the system.
+     *
+     * @param listener The listener callback that will receive change reports.
+     * @param importanceCutpoint The level of importance in which the caller is interested
+     * in differences.  For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
+     * is used here, you will receive a call each time a uids importance transitions between
+     * being <= {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE} and
+     * > {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}.
+     * @param uids The UIDs that this listener is interested with. A {@code null} value means
+     * all UIDs will be monitored by this listener, this will be equivalent to the
+     * {@link #addOnUidImportanceListener(OnUidImportanceListener, int)} in this case.
+     *
+     * @throws IllegalArgumentException If the listener is already registered.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_UID_IMPORTANCE_LISTENER_FOR_UIDS)
+    @SystemApi
+    @SuppressLint("SamShouldBeLast")
+    @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+    public void addOnUidImportanceListener(@NonNull OnUidImportanceListener listener,
+            @RunningAppProcessInfo.Importance int importanceCutpoint, @Nullable int[] uids) {
+        addOnUidImportanceListenerInternal(listener, importanceCutpoint, uids);
+    }
+
+    @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+    private void addOnUidImportanceListenerInternal(@NonNull OnUidImportanceListener listener,
+            @RunningAppProcessInfo.Importance int importanceCutpoint, @Nullable int[] uids) {
+        synchronized (mImportanceListeners) {
             if (mImportanceListeners.containsKey(listener)) {
                 throw new IllegalArgumentException("Listener already registered: " + listener);
             }
             // TODO: implement the cut point in the system process to avoid IPCs.
             MyUidObserver observer = new MyUidObserver(listener, mContext);
             try {
-                getService().registerUidObserver(observer,
+                getService().registerUidObserverForUids(observer,
                         UID_OBSERVER_PROCSTATE | UID_OBSERVER_GONE,
                         RunningAppProcessInfo.importanceToProcState(importanceCutpoint),
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), uids);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -4450,7 +4481,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
     public void removeOnUidImportanceListener(OnUidImportanceListener listener) {
-        synchronized (this) {
+        synchronized (mImportanceListeners) {
             MyUidObserver observer = mImportanceListeners.remove(listener);
             if (observer == null) {
                 throw new IllegalArgumentException("Listener not registered: " + listener);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 669baf9..4b2e93f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1540,9 +1540,16 @@
     public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
             AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
 
+    /**
+     * See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}.
+     * @hide
+     */
+    public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
+            AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 143;
+    public static final int _NUM_OP = 144;
 
     /**
      * All app ops represented as strings.
@@ -2375,6 +2382,14 @@
     public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
             "android:rapid_clear_notifications_by_listener";
 
+    /**
+     * Allows an application to read the system grammatical gender.
+     *
+     * @hide
+     */
+    public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER =
+            "android:read_system_grammatical_gender";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2486,6 +2501,7 @@
             OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
             OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
             OP_MEDIA_ROUTING_CONTROL,
+            OP_READ_SYSTEM_GRAMMATICAL_GENDER,
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2938,6 +2954,10 @@
                 OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
                 "RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER")
                 .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+                OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
+                .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
+                .build(),
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index cc3eac1..afa513d 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -26,11 +26,19 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.Xml;
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
 import android.util.proto.WireTypeMismatchException;
 
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -40,6 +48,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Describes information related to an application process's startup.
@@ -215,6 +224,11 @@
     private int mDefiningUid;
 
     /**
+     * @see #getPackageName
+     */
+    private String mPackageName;
+
+    /**
      * @see #getProcessName
      */
     private String mProcessName;
@@ -349,6 +363,14 @@
     }
 
     /**
+     * @see #getPackageName
+     * @hide
+     */
+    public void setPackageName(final String packageName) {
+        mPackageName = intern(packageName);
+    }
+
+    /**
      * @see #getProcessName
      * @hide
      */
@@ -461,6 +483,15 @@
     }
 
     /**
+     * Name of first package running in this process;
+     *
+     * @hide
+     */
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
      * The actual process name it was running with.
      *
      * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
@@ -555,6 +586,7 @@
         dest.writeInt(mRealUid);
         dest.writeInt(mPackageUid);
         dest.writeInt(mDefiningUid);
+        dest.writeString(mPackageName);
         dest.writeString(mProcessName);
         dest.writeInt(mReason);
         dest.writeInt(mStartupTimestampsNs == null ? 0 : mStartupTimestampsNs.size());
@@ -579,6 +611,7 @@
         mRealUid = other.mRealUid;
         mPackageUid = other.mPackageUid;
         mDefiningUid = other.mDefiningUid;
+        mPackageName = other.mPackageName;
         mProcessName = other.mProcessName;
         mReason = other.mReason;
         mStartupTimestampsNs = other.mStartupTimestampsNs;
@@ -593,6 +626,7 @@
         mRealUid = in.readInt();
         mPackageUid = in.readInt();
         mDefiningUid = in.readInt();
+        mPackageName = intern(in.readString());
         mProcessName = intern(in.readString());
         mReason = in.readInt();
         int starupTimestampCount = in.readInt();
@@ -624,6 +658,11 @@
                 }
             };
 
+    private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS = "timestamps";
+    private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP = "timestamp";
+    private static final String PROTO_SERIALIZER_ATTRIBUTE_KEY = "key";
+    private static final String PROTO_SERIALIZER_ATTRIBUTE_TS = "ts";
+
     /**
      * Write to a protocol buffer output stream. Protocol buffer message definition at {@link
      * android.app.ApplicationStartInfoProto}
@@ -644,9 +683,22 @@
         if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) {
             ByteArrayOutputStream timestampsBytes = new ByteArrayOutputStream();
             ObjectOutputStream timestampsOut = new ObjectOutputStream(timestampsBytes);
-            timestampsOut.writeObject(mStartupTimestampsNs);
+            TypedXmlSerializer serializer = Xml.resolveSerializer(timestampsOut);
+            serializer.startDocument(null, true);
+            serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+            for (int i = 0; i < mStartupTimestampsNs.size(); i++) {
+                serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
+                serializer.attributeInt(null, PROTO_SERIALIZER_ATTRIBUTE_KEY,
+                        mStartupTimestampsNs.keyAt(i));
+                serializer.attributeLong(null, PROTO_SERIALIZER_ATTRIBUTE_TS,
+                        mStartupTimestampsNs.valueAt(i));
+                serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
+            }
+            serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+            serializer.endDocument();
             proto.write(ApplicationStartInfoProto.STARTUP_TIMESTAMPS,
                     timestampsBytes.toByteArray());
+            timestampsOut.close();
         }
         proto.write(ApplicationStartInfoProto.START_TYPE, mStartType);
         if (mStartIntent != null) {
@@ -697,7 +749,24 @@
                     ByteArrayInputStream timestampsBytes = new ByteArrayInputStream(proto.readBytes(
                             ApplicationStartInfoProto.STARTUP_TIMESTAMPS));
                     ObjectInputStream timestampsIn = new ObjectInputStream(timestampsBytes);
-                    mStartupTimestampsNs = (ArrayMap<Integer, Long>) timestampsIn.readObject();
+                    mStartupTimestampsNs = new ArrayMap<Integer, Long>();
+                    try {
+                        TypedXmlPullParser parser = Xml.resolvePullParser(timestampsIn);
+                        XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+                        int depth = parser.getDepth();
+                        while (XmlUtils.nextElementWithin(parser, depth)) {
+                            if (PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP.equals(parser.getName())) {
+                                int key = parser.getAttributeInt(null,
+                                        PROTO_SERIALIZER_ATTRIBUTE_KEY);
+                                long ts = parser.getAttributeLong(null,
+                                        PROTO_SERIALIZER_ATTRIBUTE_TS);
+                                mStartupTimestampsNs.put(key, ts);
+                            }
+                        }
+                    } catch (XmlPullParserException e) {
+                        // Timestamps lost
+                    }
+                    timestampsIn.close();
                     break;
                 case (int) ApplicationStartInfoProto.START_TYPE:
                     mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE);
@@ -734,6 +803,7 @@
                 .append(" definingUid=").append(mDefiningUid)
                 .append(" user=").append(UserHandle.getUserId(mPackageUid))
                 .append('\n')
+                .append(" package=").append(mPackageName)
                 .append(" process=").append(mProcessName)
                 .append(" startupState=").append(mStartupState)
                 .append(" reason=").append(reasonToString(mReason))
@@ -782,4 +852,35 @@
             default -> "";
         };
     }
+
+    /** @hide */
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (other == null || !(other instanceof ApplicationStartInfo)) {
+            return false;
+        }
+        final ApplicationStartInfo o = (ApplicationStartInfo) other;
+        return mPid == o.mPid && mRealUid == o.mRealUid && mPackageUid == o.mPackageUid
+            && mDefiningUid == o.mDefiningUid && mReason == o.mReason
+            && mStartupState == o.mStartupState && mStartType == o.mStartType
+            && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName)
+            && timestampsEquals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
+                mStartType, mLaunchMode, mProcessName,
+                mStartupTimestampsNs);
+    }
+
+    private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
+        if (mStartupTimestampsNs == null && other.mStartupTimestampsNs == null) {
+            return true;
+        }
+        if (mStartupTimestampsNs == null || other.mStartupTimestampsNs == null) {
+            return false;
+        }
+        return mStartupTimestampsNs.equals(other.mStartupTimestampsNs);
+    }
 }
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index a55121a..483a6e1 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -114,7 +114,7 @@
         }
 
         try {
-            mService.setSystemWideGrammaticalGender(mContext.getUserId(), grammaticalGender);
+            mService.setSystemWideGrammaticalGender(grammaticalGender, mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -131,7 +131,8 @@
     @Configuration.GrammaticalGender
     public int getSystemGrammaticalGender() {
         try {
-            return mService.getSystemGrammaticalGender(mContext.getUserId());
+            return mService.getSystemGrammaticalGender(mContext.getAttributionSource(),
+                    mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index b5d88e8..47403d2 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -116,6 +116,7 @@
      * @throws RemoteException
      * @return Returns A binder token identifying the UidObserver registration.
      */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
     IBinder registerUidObserverForUids(in IUidObserver observer, int which, int cutpoint,
             String callingPackage, in int[] uids);
 
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
index 48a4841..86f2e91 100644
--- a/core/java/android/app/IGrammaticalInflectionManager.aidl
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -1,5 +1,22 @@
+/**
+ * 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 android.app;
 
+import android.content.AttributionSource;
 
 /**
  * Internal interface used to control app-specific gender.
@@ -20,10 +37,10 @@
      /**
       * Sets the grammatical gender to system.
       */
-     void setSystemWideGrammaticalGender(int userId, int gender);
+     void setSystemWideGrammaticalGender(int gender, int userId);
 
      /**
       * Gets the grammatical gender from system.
       */
-     int getSystemGrammaticalGender(int userId);
+     int getSystemGrammaticalGender(in AttributionSource attributionSource, int userId);
  }
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 60b34cd..3b83024 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -95,6 +95,34 @@
     int getNightModeCustomType();
 
     /**
+     * Overlays current Night Mode value.
+     * {@code attentionModeThemeOverlayType}.
+     *
+     * @param attentionModeThemeOverlayType
+     * @hide
+     */
+    @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
+    void setAttentionModeThemeOverlay(int attentionModeThemeOverlayType);
+
+
+    /**
+     * Returns current Attention Mode overlay type.
+     * <p>
+     * returns
+     *  <ul>
+     *    <li>{@link #MODE_ATTENTION_OFF}</li>
+     *    <li>{@link #MODE_ATTENTION_NIGHT}</li>
+     *    <li>{@link #MODE_ATTENTION_DAY}</li>
+     *  </ul>
+     * </p>
+     * @hide
+     */
+    @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
+    int getAttentionModeThemeOverlay();
+
+    /**
      * Sets the dark mode for the given application. This setting is persisted and will override the
      * system configuration for this application.
      *   1 - notnight mode
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 0ccb9cd..a271328 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -265,6 +266,60 @@
      */
     public static final int MODE_NIGHT_YES = 2;
 
+    /** @hide */
+    @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = {
+            MODE_ATTENTION_THEME_OVERLAY_OFF,
+            MODE_ATTENTION_THEME_OVERLAY_NIGHT,
+            MODE_ATTENTION_THEME_OVERLAY_DAY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AttentionModeThemeOverlayType {}
+
+    /** @hide */
+    @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = {
+            MODE_ATTENTION_THEME_OVERLAY_OFF,
+            MODE_ATTENTION_THEME_OVERLAY_NIGHT,
+            MODE_ATTENTION_THEME_OVERLAY_DAY,
+            MODE_ATTENTION_THEME_OVERLAY_UNKNOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AttentionModeThemeOverlayReturnType {}
+
+    /**
+     * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+     * #getAttentionModeThemeOverlay()}: Keeps night mode as set by {@link #setNightMode(int)}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @TestApi
+    public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000;
+
+    /**
+     * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+     * #getAttentionModeThemeOverlay()}: Maintains night mode always on.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @TestApi
+    public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001;
+
+    /**
+     * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+     * #getAttentionModeThemeOverlay()}: Maintains night mode always off (Light).
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @TestApi
+    public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002;
+
+    /**
+     * Constant for {@link #getAttentionModeThemeOverlay()}: Error communication with server.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @TestApi
+    public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1;
+
     /**
      * Granular types for {@link #setNightModeCustomType(int)}
      * @hide
@@ -733,6 +788,55 @@
     }
 
     /**
+     * Overlays current Attention mode Night Mode overlay.
+     * {@code attentionModeThemeOverlayType}.
+     *
+     * @throws IllegalArgumentException if passed an unsupported type to
+     *                                  {@code AttentionModeThemeOverlayType}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+    public void setAttentionModeThemeOverlay(
+            @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
+        if (sGlobals != null) {
+            try {
+                sGlobals.mService.setAttentionModeThemeOverlay(attentionModeThemeOverlayType);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns the currently configured Attention Mode theme overlay.
+     * <p>
+     * May be one of:
+     *   <ul>
+     *     <li>{@link #MODE_ATTENTION_THEME_OVERLAY_OFF}</li>
+     *     <li>{@link #MODE_ATTENTION_THEME_OVERLAY_NIGHT}</li>
+     *     <li>{@link #MODE_ATTENTION_THEME_OVERLAY_DAY}</li>
+     *     <li>{@link #MODE_ATTENTION_THEME_OVERLAY_UNKNOWN}</li>
+     *   </ul>
+     * </p>
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+    public @AttentionModeThemeOverlayReturnType int getAttentionModeThemeOverlay() {
+        if (sGlobals != null) {
+            try {
+                return sGlobals.mService.getAttentionModeThemeOverlay();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return MODE_ATTENTION_THEME_OVERLAY_UNKNOWN;
+    }
+
+    /**
      * Sets and persist the night mode for this application.
      * <p>
      * The mode can be one of:
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 4fc25fd..c0b299b 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -19,4 +19,11 @@
     name: "app_restrictions_api"
     description: "API to track and query restrictions applied to apps"
     bug: "320150834"
-}
\ No newline at end of file
+}
+
+flag {
+     namespace: "backstage_power"
+     name: "uid_importance_listener_for_uids"
+     description: "API to add OnUidImportanceListener with targetted UIDs"
+     bug: "286258140"
+}
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
index f94987e..5ab7991 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEvent.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -89,8 +89,11 @@
      */
     public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name";
 
-    /** Default value for the rate per minute data field. */
-    private static final int RATE_PER_MINUTE_UNKNOWN = -1;
+    /**
+     * Default value for {@link #getRatePerMinute}. Indicates that the rate of the event is unknown.
+     */
+    @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
+    public static final int RATE_PER_MINUTE_UNKNOWN = -1;
 
     /** @hide */
     @IntDef(prefix = { "EVENT_" }, value = {
@@ -636,10 +639,10 @@
     }
 
     @DataClass.Generated(
-            time = 1704895515931L,
+            time = 1705575046107L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java",
-            inputSignatures = "public static final  int EVENT_UNKNOWN\npublic static final  int EVENT_COUGH\npublic static final  int EVENT_SNORE\npublic static final  int EVENT_BACK_DOUBLE_TAP\npublic static final @android.app.ambientcontext.AmbientContextEvent.Event @android.annotation.FlaggedApi int EVENT_HEART_RATE\npublic static final  int EVENT_VENDOR_WEARABLE_START\npublic static final  java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\nprivate static final  int RATE_PER_MINUTE_UNKNOWN\npublic static final  int LEVEL_UNKNOWN\npublic static final  int LEVEL_LOW\npublic static final  int LEVEL_MEDIUM_LOW\npublic static final  int LEVEL_MEDIUM\npublic static final  int LEVEL_MEDIUM_HIGH\npublic static final  int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate final @android.annotation.IntRange int mRatePerMinute\nprivate static  int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultDensityLevel()\nprivate static  android.os.PersistableBundle defaultVendorData()\nprivate static  int defaultRatePerMinute()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+            inputSignatures = "public static final  int EVENT_UNKNOWN\npublic static final  int EVENT_COUGH\npublic static final  int EVENT_SNORE\npublic static final  int EVENT_BACK_DOUBLE_TAP\npublic static final @android.app.ambientcontext.AmbientContextEvent.Event @android.annotation.FlaggedApi int EVENT_HEART_RATE\npublic static final  int EVENT_VENDOR_WEARABLE_START\npublic static final  java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final @android.annotation.FlaggedApi int RATE_PER_MINUTE_UNKNOWN\npublic static final  int LEVEL_UNKNOWN\npublic static final  int LEVEL_LOW\npublic static final  int LEVEL_MEDIUM_LOW\npublic static final  int LEVEL_MEDIUM\npublic static final  int LEVEL_MEDIUM_HIGH\npublic static final  int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate final @android.annotation.IntRange int mRatePerMinute\nprivate static  int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultDensityLevel()\nprivate static  android.os.PersistableBundle defaultVendorData()\nprivate static  int defaultRatePerMinute()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\[email protected](genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java
index 3cd2923..df6324f 100644
--- a/core/java/android/app/usage/UsageEventsQuery.java
+++ b/core/java/android/app/usage/UsageEventsQuery.java
@@ -29,9 +29,6 @@
 import com.android.internal.util.ArrayUtils;
 
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
 
 /**
  * An Object-Oriented representation for a {@link UsageEvents} query.
@@ -77,19 +74,17 @@
     }
 
     /**
-     * Returns the set of usage event types for the query.
-     * <em>Note: An empty set indicates query for all usage events. </em>
+     * Retrieves the usage event types for the query.
+     * <p>Note that an empty array indicates querying all usage event types, and it may
+     * cause additional system overhead when calling
+     * {@link UsageStatsManager#queryEvents(UsageEventsQuery)}. Apps are encouraged to
+     * provide a list of event types via {@link Builder#setEventTypes(int...)}</p>
+     *
+     * @return an array contains the usage event types that was previously set using
+     *         {@link Builder#setEventTypes(int...)} or an empty array if no value has been set.
      */
-    public @NonNull Set<Integer> getEventTypes() {
-        if (ArrayUtils.isEmpty(mEventTypes)) {
-            return Collections.emptySet();
-        }
-
-        HashSet<Integer> eventTypeSet = new HashSet<>();
-        for (int eventType : mEventTypes) {
-            eventTypeSet.add(eventType);
-        }
-        return eventTypeSet;
+    public @NonNull @Event.EventType int[] getEventTypes() {
+        return Arrays.copyOf(mEventTypes, mEventTypes.length);
     }
 
     /** @hide */
@@ -125,11 +120,6 @@
                 }
             };
 
-    /** @hide */
-    public int[] getEventTypeFilter() {
-        return Arrays.copyOf(mEventTypes, mEventTypes.length);
-    }
-
     /**
      * Builder for UsageEventsQuery.
      */
@@ -166,12 +156,25 @@
         }
 
         /**
-         * Specifies the list of usage event types to be included in the query.
-         * @param eventTypes List of the usage event types. See {@link UsageEvents.Event}
+         * Sets the list of usage event types to be included in the query.
          *
-         * @throws llegalArgumentException if the event type is not valid.
+         * <p>Note: </p> An empty array will be returned by
+         * {@link UsageEventsQuery#getEventTypes()} without calling this method, which indicates
+         * querying for all event types. Apps are encouraged to provide a list of event types.
+         * Only the matching types supplied will be used to query.
+         *
+         * @param eventTypes the array of the usage event types. See {@link UsageEvents.Event}.
+         * @throws NullPointerException if {@code eventTypes} is {@code null} or empty.
+         * @throws IllegalArgumentException if any of event types are invalid.
+         * @see UsageEventsQuery#getEventTypes()
+         * @see UsageStatsManager#queryEvents(UsageEventsQuery)
          */
-        public @NonNull Builder addEventTypes(@NonNull @Event.EventType int... eventTypes) {
+        public @NonNull Builder setEventTypes(@NonNull @Event.EventType int... eventTypes) {
+            if (eventTypes == null || eventTypes.length == 0) {
+                throw new NullPointerException("eventTypes is null or empty");
+            }
+
+            mEventTypes.clear();
             for (int i = 0; i < eventTypes.length; i++) {
                 final int eventType = eventTypes[i];
                 if (eventType < Event.NONE || eventType > Event.MAX_EVENT_TYPE) {
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 85d223d..8df913a 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -602,14 +602,15 @@
 
     /**
      * Query for events with specific UsageEventsQuery object.
+     *
      * <em>Note: if the user's device is not in an unlocked state (as defined by
      * {@link UserManager#isUserUnlocked()}), then {@code null} will be returned.</em>
      *
      * @param query The query object used to specify the query parameters.
-     * @return A {@link UsageEvents}.
+     * @return A {@link UsageEvents} which contains the events matching the query parameters.
      */
     @FlaggedApi(Flags.FLAG_FILTER_BASED_EVENT_QUERY_API)
-    @NonNull
+    @Nullable
     @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
     public UsageEvents queryEvents(@NonNull UsageEventsQuery query) {
         try {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 879656a..672e3439 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -76,6 +76,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -909,34 +910,20 @@
     }
 
     /**
-     * Listener for any changes to the list of attached transports.
-     *
-     * @see com.android.server.companion.transport.Transport
-     *
-     * @hide
-     */
-    public interface OnTransportsChangedListener {
-        /**
-         * Invoked when a transport is attached or detached.
-         *
-         * @param associations all the associations which have connected transports.
-         */
-        void onTransportsChanged(@NonNull List<AssociationInfo> associations);
-    }
-
-    /**
      * Adds a listener for any changes to the list of attached transports.
-     * {@link OnTransportsChangedListener#onTransportsChanged(List)} will be triggered with a list
-     * of existing transports when a transport is detached or a new transport is attached.
+     * Registered listener will be triggered with a list of existing transports when a transport
+     * is detached or a new transport is attached.
      *
+     * @param executor The executor which will be used to invoke the listener.
+     * @param listener Called when a transport is attached or detached. Contains the updated list of
+     *                 associations which have connected transports.
      * @see com.android.server.companion.transport.Transport
-     *
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void addOnTransportsChangedListener(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnTransportsChangedListener listener) {
+            @NonNull Consumer<List<AssociationInfo>> listener) {
         final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
                 executor, listener);
         try {
@@ -955,7 +942,7 @@
      */
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnTransportsChangedListener(
-            @NonNull OnTransportsChangedListener listener) {
+            @NonNull Consumer<List<AssociationInfo>> listener) {
         final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
                 null, listener);
         try {
@@ -983,28 +970,18 @@
     }
 
     /**
-     * Listener that triggers a callback when a message is received through a connected transport.
+     * Adds a listener that triggers when messages of given type are received.
      *
-     * @see #addOnMessageReceivedListener(Executor, int, OnMessageReceivedListener)
-     *
-     * @hide
-     */
-    public interface OnMessageReceivedListener {
-        /**
-         * Called when a message is received.
-         */
-        void onMessageReceived(int associationId, @NonNull byte[] data);
-    }
-
-    /**
-     * Adds a listener to trigger callbacks when messages of given type are received.
-     *
+     * @param executor The executor which will be used to invoke the listener.
+     * @param messageType Message type to be subscribed to.
+     * @param listener Called when a message is received. Contains the association ID of the message
+     *                 sender and the message payload as a byte array.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void addOnMessageReceivedListener(
             @NonNull @CallbackExecutor Executor executor, int messageType,
-            @NonNull OnMessageReceivedListener listener) {
+            @NonNull BiConsumer<Integer, byte[]> listener) {
         final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
                 executor, listener);
         try {
@@ -1021,7 +998,7 @@
      */
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnMessageReceivedListener(int messageType,
-            @NonNull OnMessageReceivedListener listener) {
+            @NonNull BiConsumer<Integer, byte[]> listener) {
         final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
                 null, listener);
         try {
@@ -1596,34 +1573,34 @@
     private static class OnTransportsChangedListenerProxy
             extends IOnTransportsChangedListener.Stub {
         private final Executor mExecutor;
-        private final OnTransportsChangedListener mListener;
+        private final Consumer<List<AssociationInfo>> mListener;
 
         private OnTransportsChangedListenerProxy(Executor executor,
-                OnTransportsChangedListener listener) {
+                Consumer<List<AssociationInfo>> listener) {
             mExecutor = executor;
             mListener = listener;
         }
 
         @Override
         public void onTransportsChanged(@NonNull List<AssociationInfo> associations) {
-            mExecutor.execute(() -> mListener.onTransportsChanged(associations));
+            mExecutor.execute(() -> mListener.accept(associations));
         }
     }
 
     private static class OnMessageReceivedListenerProxy
             extends IOnMessageReceivedListener.Stub {
         private final Executor mExecutor;
-        private final OnMessageReceivedListener mListener;
+        private final BiConsumer<Integer, byte[]> mListener;
 
         private OnMessageReceivedListenerProxy(Executor executor,
-                OnMessageReceivedListener listener) {
+                BiConsumer<Integer, byte[]> listener) {
             mExecutor = executor;
             mListener = listener;
         }
 
         @Override
         public void onMessageReceived(int associationId, byte[] data) {
-            mExecutor.execute(() -> mListener.onMessageReceived(associationId, data));
+            mExecutor.execute(() -> mListener.accept(associationId, data));
         }
     }
 
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 0493312..325aa28f 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -78,6 +78,11 @@
     int getDeviceIdForDisplayId(int displayId);
 
     /**
+     * Returns the display name corresponding to the given persistent device ID, if any.
+     */
+    CharSequence getDisplayNameForPersistentDeviceId(in String persistentDeviceId);
+
+    /**
      * Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
      * {@link VirtualDeviceManager#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
      * device which is not a virtual device. {@code deviceId} must correspond to a virtual device
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index a4cada2..90d251b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -361,6 +361,34 @@
     }
 
     /**
+     * Get the display name for a given persistent device ID.
+     *
+     * <p>This will work even if currently there is no valid virtual device with the given
+     * persistent ID, as long as such a device has been created or can be created.</p>
+     *
+     * @return the display name associated with the given persistent device ID, or {@code null} if
+     *     the persistent ID is invalid or does not correspond to a virtual device.
+     *
+     * @hide
+     */
+    // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId()
+    @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API)
+    @SystemApi
+    @Nullable
+    public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String persistentDeviceId) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
+            return null;
+        }
+        try {
+            return mService.getDisplayNameForPersistentDeviceId(
+                    Objects.requireNonNull(persistentDeviceId));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
      * {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
      * device which is not a virtual device. {@code deviceId} must correspond to a virtual device
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 32ecb58..1f25fd0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -80,7 +80,7 @@
             long timeout);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
-    void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle, int flags);
+    void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
     void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 43322641..f0efed9 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2352,7 +2352,6 @@
      * communicated.
      *
      * @param statusReceiver Callback used to notify when the operation is completed.
-     * @param flags Flags for archiving. Can be 0 or {@link PackageManager#DELETE_SHOW_DIALOG}.
      * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
      *                                              available to the caller or isn't archived.
      */
@@ -2360,12 +2359,11 @@
             Manifest.permission.DELETE_PACKAGES,
             Manifest.permission.REQUEST_DELETE_PACKAGES})
     @FlaggedApi(Flags.FLAG_ARCHIVING)
-    public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver,
-            @DeleteFlags int flags)
+    public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
             throws PackageManager.NameNotFoundException {
         try {
             mInstaller.requestArchive(packageName, mInstallerPackageName, statusReceiver,
-                    new UserHandle(mUserId), flags);
+                    new UserHandle(mUserId));
         } catch (ParcelableException e) {
             e.maybeRethrow(PackageManager.NameNotFoundException.class);
         } catch (RemoteException e) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8e5e825..aabbe69 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2596,7 +2596,6 @@
             DELETE_SYSTEM_APP,
             DELETE_DONT_KILL_APP,
             DELETE_CHATTY,
-            DELETE_SHOW_DIALOG,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DeleteFlags {}
@@ -2649,12 +2648,6 @@
     public static final int DELETE_ARCHIVE = 0x00000010;
 
     /**
-     * Show a confirmation dialog to the user when app is being deleted.
-     */
-    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
-    public static final int DELETE_SHOW_DIALOG = 0x00000020;
-
-    /**
      * Flag parameter for {@link #deletePackage} to indicate that package deletion
      * should be chatty.
      *
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index c7797c7..7c5d305 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -70,4 +70,11 @@
     namespace: "profile_experiences"
     description: "Add support for Private Space in resolver sheet"
     bug: "307515485"
+}
+
+flag {
+    name: "move_quiet_mode_operations_to_separate_thread"
+    namespace: "profile_experiences"
+    description: "Move the quiet mode operations, happening on a background thread today, to a separate thread."
+    bug: "320483504"
 }
\ No newline at end of file
diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java
index 088949e..f4312a9 100644
--- a/core/java/android/content/res/FontScaleConverter.java
+++ b/core/java/android/content/res/FontScaleConverter.java
@@ -17,7 +17,9 @@
 package android.content.res;
 
 
+import android.annotation.AnyThread;
 import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
 
 /**
  * A converter for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
@@ -40,4 +42,35 @@
      * Converts a dimension in "dp" back to "sp".
      */
     float convertDpToSp(float dp);
+
+    /**
+     * Returns true if non-linear font scaling curves would be in effect for the given scale, false
+     * if the scaling would follow a linear curve or for no scaling.
+     *
+     * <p>Example usage: {@code
+     * isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)}
+     */
+    @AnyThread
+    static boolean isNonLinearFontScalingActive(float fontScale) {
+        return FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale);
+    }
+
+    /**
+     * Finds a matching FontScaleConverter for the given fontScale factor.
+     *
+     * Generally you shouldn't need this; you can use {@link
+     * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do
+     * the scaling conversion for you. Dimens and resources loaded from XML will also be
+     * automatically converted. But for UI frameworks or other situations where you need to do the
+     * conversion without an Android Context, you can use this method.
+     *
+     * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
+     *
+     * @return a converter for the given scale, or null if non-linear scaling should not be used.
+     */
+    @Nullable
+    @AnyThread
+    static FontScaleConverter forScale(float fontScale) {
+        return FontScaleConverterFactory.forScale(fontScale);
+    }
 }
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 5d31cc0..cbe4c62 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -17,7 +17,6 @@
 package android.content.res;
 
 import android.annotation.AnyThread;
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.MathUtils;
@@ -32,8 +31,9 @@
  * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do the
  * scaling conversion for you. But for UI frameworks or other situations where you need to do the
  * conversion without an Android Context, you can use this class.
+ *
+ * @hide
  */
-@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
 public class FontScaleConverterFactory {
     private static final float SCALE_KEY_MULTIPLIER = 100f;
 
@@ -124,7 +124,6 @@
      * <p>Example usage:
      * <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code>
      */
-    @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
     @AnyThread
     public static boolean isNonLinearFontScalingActive(float fontScale) {
         return fontScale >= sMinScaleBeforeCurvesApplied;
@@ -137,7 +136,6 @@
      *
      * @return a converter for the given scale, or null if non-linear scaling should not be used.
      */
-    @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
     @Nullable
     @AnyThread
     public static FontScaleConverter forScale(float fontScale) {
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 1165f9a..90cd471 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -40,4 +40,11 @@
     name: "selector_ui_improvements_enabled"
     description: "Enables Credential Selector UI improvements for VIC"
     bug: "319448437"
+}
+
+flag {
+    namespace: "credential_manager"
+    name: "configurable_selector_ui_enabled"
+    description: "Enables OEM configurable Credential Selector UI"
+    bug: "319448437"
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index a0f4d8d..c0424db 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -16,6 +16,7 @@
 
 package android.hardware.biometrics;
 
+import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -25,6 +26,7 @@
 import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.DrawableRes;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -33,6 +35,7 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.graphics.Bitmap;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
@@ -160,6 +163,45 @@
         }
 
         /**
+         * Optional: Sets the drawable resource of the logo that will be shown on the prompt.
+         *
+         * <p> Note that using this method is not recommended in most scenarios because the calling
+         * application's icon will be used by default. Setting the logo is intended for large
+         * bundled applications that perform a wide range of functions and need to show distinct
+         * icons for each function.
+         *
+         * @param logoRes A drawable resource of the logo that will be shown on the prompt.
+         * @return This builder.
+         */
+        @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+        @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+        @NonNull
+        public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) {
+            mPromptInfo.setLogoRes(logoRes);
+            return this;
+        }
+
+        /**
+         * Optional: Sets the bitmap drawable of the logo that will be shown on the prompt.
+         *
+         * <p> Note that using this method is not recommended in most scenarios because the calling
+         * application's icon will be used by default. Setting the logo is intended for large
+         * bundled applications that perform a wide range of functions and need to show distinct
+         * icons for each function.
+         *
+         * @param logoBitmap A bitmap drawable of the logo that will be shown on the prompt.
+         * @return This builder.
+         */
+        @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+        @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+        @NonNull
+        public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) {
+            mPromptInfo.setLogoBitmap(logoBitmap);
+            return this;
+        }
+
+
+        /**
          * Required: Sets the title that will be shown on the prompt.
          * @param title The title to display.
          * @return This builder.
@@ -676,6 +718,34 @@
     }
 
     /**
+     * Gets the drawable resource of the logo for the prompt, as set by
+     * {@link Builder#setLogo(int)}. Currently for system applications use only.
+     *
+     * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set.
+     */
+    @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+    @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+    @DrawableRes
+    public int getLogoRes() {
+        return mPromptInfo.getLogoRes();
+    }
+
+    /**
+     * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for
+     * system applications use only.
+     *
+     * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
+     */
+    @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+    @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+    @Nullable
+    public Bitmap getLogoBitmap() {
+        return mPromptInfo.getLogoBitmap();
+    }
+
+
+
+    /**
      * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}.
      * @return The title of the prompt, which is guaranteed to be non-null.
      */
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index c73ebd4..d788b37 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -16,8 +16,10 @@
 
 package android.hardware.biometrics;
 
+import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.Bitmap;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -30,6 +32,8 @@
  */
 public class PromptInfo implements Parcelable {
 
+    @DrawableRes private int mLogoRes = -1;
+    @Nullable private Bitmap mLogoBitmap;
     @NonNull private CharSequence mTitle;
     private boolean mUseDefaultTitle;
     @Nullable private CharSequence mSubtitle;
@@ -56,6 +60,8 @@
     }
 
     PromptInfo(Parcel in) {
+        mLogoRes = in.readInt();
+        mLogoBitmap = in.readTypedObject(Bitmap.CREATOR);
         mTitle = in.readCharSequence();
         mUseDefaultTitle = in.readBoolean();
         mSubtitle = in.readCharSequence();
@@ -98,6 +104,8 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mLogoRes);
+        dest.writeTypedObject(mLogoBitmap, 0);
         dest.writeCharSequence(mTitle);
         dest.writeBoolean(mUseDefaultTitle);
         dest.writeCharSequence(mSubtitle);
@@ -156,9 +164,30 @@
         }
         return false;
     }
+
+    /**
+     * Returns whether MANAGE_BIOMETRIC_DIALOG is contained.
+     */
+    public boolean containsManageBioApiConfigurations() {
+        if (mLogoRes != -1) {
+            return true;
+        } else if (mLogoBitmap != null) {
+            return true;
+        }
+        return false;
+    }
     // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
 
     // Setters
+    public void setLogoRes(@DrawableRes int logoRes) {
+        mLogoRes = logoRes;
+        checkOnlyOneLogoSet();
+    }
+
+    public void setLogoBitmap(@NonNull Bitmap logoBitmap) {
+        mLogoBitmap = logoBitmap;
+        checkOnlyOneLogoSet();
+    }
 
     public void setTitle(CharSequence title) {
         mTitle = title;
@@ -244,6 +273,14 @@
     }
 
     // Getters
+    @DrawableRes
+    public int getLogoRes() {
+        return mLogoRes;
+    }
+
+    public Bitmap getLogoBitmap() {
+        return mLogoBitmap;
+    }
 
     public CharSequence getTitle() {
         return mTitle;
@@ -337,4 +374,11 @@
     public boolean isShowEmergencyCallButton() {
         return mShowEmergencyCallButton;
     }
+
+    private void checkOnlyOneLogoSet() {
+        if (mLogoRes != -1 && mLogoBitmap != null) {
+            throw new IllegalStateException(
+                    "Exclusively one of logo resource or logo bitmap can be set");
+        }
+    }
 }
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 25fba60..b9bb059 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -17,9 +17,11 @@
 package android.os;
 
 import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC;
+import static android.os.Flags.FLAG_BATTERY_PART_STATUS_API;
 
 import android.Manifest.permission;
 import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -236,6 +238,31 @@
     public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
                                             OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
 
+    // values for "battery part status" property
+    /**
+     * Battery part status is not supported.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final int PART_STATUS_UNSUPPORTED = 0;
+
+    /**
+     * Battery is the original device battery.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final int PART_STATUS_ORIGINAL = 1;
+
+    /**
+     * Battery has been replaced.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final int PART_STATUS_REPLACED = 2;
+
     /** @hide */
     @SuppressLint("UnflaggedApi") // TestApi without associated feature.
     @TestApi
@@ -366,6 +393,32 @@
     @FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC)
     public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10;
 
+    /**
+     * Battery part serial number.
+     *
+     * <p class="note">
+     * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+     *
+     * @hide
+     */
+    @RequiresPermission(permission.BATTERY_STATS)
+    @SystemApi
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11;
+
+    /**
+     * Battery part status from a BATTERY_PART_STATUS_* value.
+     *
+     * <p class="note">
+     * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+     *
+     * @hide
+     */
+    @RequiresPermission(permission.BATTERY_STATS)
+    @SystemApi
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final int BATTERY_PROPERTY_PART_STATUS = 12;
+
     private final Context mContext;
     private final IBatteryStats mBatteryStats;
     private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
@@ -431,6 +484,25 @@
     }
 
     /**
+     * Same as queryProperty, but for strings.
+     */
+    private String queryStringProperty(int id) {
+        if (mBatteryPropertiesRegistrar == null) {
+            return null;
+        }
+
+        try {
+            BatteryProperty prop = new BatteryProperty();
+            if (mBatteryPropertiesRegistrar.getProperty(id, prop) == 0) {
+                return prop.getString();
+            }
+            return null;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return the value of a battery property of integer type.
      *
      * @param id identifier of the requested property
@@ -464,6 +536,21 @@
     }
 
     /**
+     * Return the value of a battery property of String type. If the
+     * platform does not provide the property queried, this value will
+     * be null.
+     *
+     * @param id identifier of the requested property.
+     *
+     * @return the property value, or null if not supported.
+     */
+    @Nullable
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public String getStringProperty(int id) {
+        return queryStringProperty(id);
+    }
+
+    /**
      * Return true if the plugType given is wired
      * @param plugType {@link #BATTERY_PLUGGED_AC}, {@link #BATTERY_PLUGGED_USB},
      *        or {@link #BATTERY_PLUGGED_WIRELESS}
diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java
index b40988a..464577f 100644
--- a/core/java/android/os/BatteryProperty.java
+++ b/core/java/android/os/BatteryProperty.java
@@ -28,12 +28,14 @@
  */
 public class BatteryProperty implements Parcelable {
     private long mValueLong;
+    private String mValueString;
 
     /**
      * @hide
      */
     public BatteryProperty() {
         mValueLong = Long.MIN_VALUE;
+        mValueString = null;
     }
 
     /**
@@ -46,14 +48,23 @@
     /**
      * @hide
      */
+    public String getString() {
+        return mValueString;
+    }
+
+    /**
+     * @hide
+     */
     public void setLong(long val) {
         mValueLong = val;
     }
 
-    /*
-     * Parcel read/write code must be kept in sync with
-     * frameworks/native/services/batteryservice/BatteryProperty.cpp
+    /**
+     * @hide
      */
+    public void setString(String val) {
+        mValueString = val;
+    }
 
     private BatteryProperty(Parcel p) {
         readFromParcel(p);
@@ -61,10 +72,12 @@
 
     public void readFromParcel(Parcel p) {
         mValueLong = p.readLong();
+        mValueString = p.readString8();
     }
 
     public void writeToParcel(Parcel p, int flags) {
         p.writeLong(mValueLong);
+        p.writeString8(mValueString);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<BatteryProperty> CREATOR
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 0db90bf..82518bf 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -106,3 +106,11 @@
     bug: "305311707"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "battery_part_status_api"
+    namespace: "phoenix"
+    description: "Feature flag for adding Health HAL v3 APIs."
+    is_fixed_read_only: true
+    bug: "309792384"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7d84bb3..58159c2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3176,15 +3176,7 @@
         }
 
         public void destroy() {
-            try {
-                // If this process is the system server process, mArray is the same object as
-                // the memory int array kept inside SettingsProvider, so skipping the close()
-                if (!Settings.isInSystemServer() && !mArray.isClosed()) {
-                    mArray.close();
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "Error closing backing array", e);
-            }
+            maybeCloseGenerationArray(mArray);
         }
 
         @Override
@@ -3197,6 +3189,21 @@
         }
     }
 
+    private static void maybeCloseGenerationArray(@Nullable MemoryIntArray array) {
+        if (array == null) {
+            return;
+        }
+        try {
+            // If this process is the system server process, the MemoryIntArray received from Parcel
+            // is the same object as the one kept inside SettingsProvider, so skipping the close().
+            if (!Settings.isInSystemServer() && !array.isClosed()) {
+                array.close();
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Error closing the generation tracking array", e);
+        }
+    }
+
     private static final class ContentProviderHolder {
         private final Object mLock = new Object();
 
@@ -3496,6 +3503,8 @@
                                         mGenerationTrackers.put(name, new GenerationTracker(name,
                                                 array, index, generation,
                                                 mGenerationTrackerErrorHandler));
+                                    } else {
+                                        maybeCloseGenerationArray(array);
                                     }
                                 }
                                 if (mGenerationTrackers.get(name) != null
@@ -3733,6 +3742,8 @@
                                     new GenerationTracker(prefix, array, index, generation,
                                             mGenerationTrackerErrorHandler));
                             currentGeneration = generation;
+                        } else {
+                            maybeCloseGenerationArray(array);
                         }
                     }
                     if (mGenerationTrackers.get(prefix) != null && currentGeneration
@@ -7310,6 +7321,28 @@
                 "bluetooth_le_broadcast_app_source_name";
 
         /**
+         * This is used by LocalBluetoothLeBroadcast to downgrade the broadcast quality to improve
+         * compatibility.
+         *
+         * <ul>
+         *   <li>0 = false
+         *   <li>1 = true
+         * </ul>
+         *
+         * @hide
+         */
+        public static final String BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY =
+                "bluetooth_le_broadcast_improve_compatibility";
+
+        /**
+         * This is used by LocalBluetoothLeBroadcast to store the fallback active device address.
+         *
+         * @hide
+         */
+        public static final String BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS =
+                "bluetooth_le_broadcast_fallback_active_device_address";
+
+        /**
          * Ringtone routing value for hearing aid. It routes ringtone to hearing aid or device
          * speaker.
          * <ul>
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index 8b4a99e..d5ac7a7 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -28,5 +28,10 @@
                 }
             ]
         }
+    ],
+    "postsubmit": [
+        {
+            "name": "CtsDeviceConfigTestCases"
+        }
     ]
 }
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 30524a1..1994058 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -53,7 +53,7 @@
 
 flag {
     name: "frp_enforcement"
-    namespace: "android_hw_security"
+    namespace: "hardware_backed_security"
     description: "This flag controls whether PDB enforces FRP"
     bug: "290312729"
     is_fixed_read_only: true
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0de4505..8935ab3 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -16,9 +16,11 @@
 package android.telephony;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -50,6 +52,7 @@
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.ITelephonyRegistry;
+import com.android.server.telecom.flags.Flags;
 
 import java.lang.ref.WeakReference;
 import java.util.Arrays;
@@ -66,11 +69,13 @@
  * or {@link PhoneCapability} changed. This might trigger callback from applications side through
  * {@link android.telephony.PhoneStateListener}
  *
- * TODO: limit API access to only carrier apps with certain permissions or apps running on
+ * Limit API access to only carrier apps with certain permissions or apps running on
  * privileged UID.
  *
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
 public class TelephonyRegistryManager {
 
     private static final String TAG = "TelephonyRegistryManager";
@@ -120,6 +125,7 @@
      * @param listener an instance of {@link SubscriptionManager.OnSubscriptionsChangedListener}
      *                 with onSubscriptionsChanged overridden.
      * @param executor the executor that will execute callbacks.
+     * @hide
      */
     public void addOnSubscriptionsChangedListener(
             @NonNull SubscriptionManager.OnSubscriptionsChangedListener listener,
@@ -155,6 +161,7 @@
      * invoke the listener fails.
      *
      * @param listener that is to be unregistered.
+     * @hide
      */
     public void removeOnSubscriptionsChangedListener(
             @NonNull SubscriptionManager.OnSubscriptionsChangedListener listener) {
@@ -180,6 +187,7 @@
      * {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} with
      *                 onOpportunisticSubscriptionsChanged overridden.
      * @param executor an Executor that will execute callbacks.
+     * @hide
      */
     public void addOnOpportunisticSubscriptionsChangedListener(
             @NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener,
@@ -221,6 +229,7 @@
      * listener fails.
      *
      * @param listener that is to be unregistered.
+     * @hide
      */
     public void removeOnOpportunisticSubscriptionsChangedListener(
             @NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener) {
@@ -252,6 +261,7 @@
      * @param listener Listener providing callback
      * @param events Events
      * @param notifyNow Whether to notify instantly
+     * @hide
      */
     public void listenFromListener(int subId, @NonNull boolean renounceFineLocationAccess,
             @NonNull boolean renounceCoarseLocationAccess, @NonNull String pkg,
@@ -318,6 +328,7 @@
      * @param active Whether the carrier network change is or shortly will be
      * active. Set this value to true to begin showing alternative UI and false to stop.
      * @see TelephonyManager#hasCarrierPrivileges
+     * @hide
      */
     public void notifyCarrierNetworkChange(boolean active) {
         try {
@@ -344,6 +355,7 @@
      * @param active whether the carrier network change is or shortly will be active. Set this value
      *              to true to begin showing alternative UI and false to stop.
      * @see TelephonyManager#hasCarrierPrivileges
+     * @hide
      */
     public void notifyCarrierNetworkChange(int subscriptionId, boolean active) {
         try {
@@ -362,6 +374,7 @@
      * @param subId for which call state changed.
      * @param state latest call state. e.g, offhook, ringing
      * @param incomingNumber incoming phone number.
+     * @hide
      */
     public void notifyCallStateChanged(int slotIndex, int subId, @CallState int state,
             @Nullable String incomingNumber) {
@@ -380,6 +393,7 @@
      * @param incomingNumber incoming phone number.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void notifyCallStateChangedForAllSubscriptions(@CallState int state,
             @Nullable String incomingNumber) {
@@ -424,6 +438,7 @@
      * subId is invalid.
      * @param subId for which the service state changed.
      * @param state service state e.g, in service, out of service or roaming status.
+     * @hide
      */
     public void notifyServiceStateChanged(int slotIndex, int subId, @NonNull ServiceState state) {
         try {
@@ -441,6 +456,7 @@
      * subId is invalid.
      * @param subId for which the signalstrength changed.
      * @param signalStrength e.g, signalstrength level {@see SignalStrength#getLevel()}
+     * @hide
      */
     public void notifySignalStrengthChanged(int slotIndex, int subId,
             @NonNull SignalStrength signalStrength) {
@@ -461,6 +477,7 @@
      * @param subId for which message waiting indicator changed.
      * @param msgWaitingInd {@code true} indicates there is message-waiting indicator, {@code false}
      * otherwise.
+     * @hide
      */
     public void notifyMessageWaitingChanged(int slotIndex, int subId, boolean msgWaitingInd) {
         try {
@@ -477,6 +494,7 @@
      * @param subId for which call forwarding status changed.
      * @param callForwardInd {@code true} indicates there is call forwarding, {@code false}
      * otherwise.
+     * @hide
      */
     public void notifyCallForwardingChanged(int subId, boolean callForwardInd) {
         try {
@@ -493,6 +511,7 @@
      * @param subId for which data activity state changed.
      * @param dataActivityType indicates the latest data activity type e.g. {@link
      * TelephonyManager#DATA_ACTIVITY_IN}
+     * @hide
      */
     public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) {
         try {
@@ -511,6 +530,7 @@
      * @param subId for which data activity state changed.
      * @param dataActivityType indicates the latest data activity type e.g. {@link
      * TelephonyManager#DATA_ACTIVITY_IN}
+     * @hide
      */
     public void notifyDataActivityChanged(int slotIndex, int subId,
             @DataActivityType int dataActivityType) {
@@ -532,6 +552,7 @@
      *
      * @see PreciseDataConnectionState
      * @see TelephonyManager#DATA_DISCONNECTED
+     * @hide
      */
     public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
             @NonNull PreciseDataConnectionState preciseState) {
@@ -552,6 +573,7 @@
      * @param subId for which call quality state changed.
      * @param callQuality Information about call quality e.g, call quality level
      * @param networkType associated with this data connection. e.g, LTE
+     * @hide
      */
     public void notifyCallQualityChanged(int slotIndex, int subId, @NonNull CallQuality callQuality,
         @NetworkType int networkType) {
@@ -573,6 +595,7 @@
      * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_JITTER_INT}
      *
      * @param status media quality status
+     * @hide
      */
     public void notifyMediaQualityStatusChanged(
             int slotIndex, int subId, @NonNull MediaQualityStatus status) {
@@ -590,6 +613,7 @@
      * @param slotIndex for which emergency number list changed. Can be derived from subId except
      * when subId is invalid.
      * @param subId for which emergency number list changed.
+     * @hide
      */
     public void notifyEmergencyNumberList( int slotIndex, int subId) {
         try {
@@ -602,14 +626,16 @@
 
     /**
      * Notify outgoing emergency call.
-     * @param phoneId Sender phone ID.
+     * @param simSlotIndex Sender phone ID.
      * @param subId Sender subscription ID.
      * @param emergencyNumber Emergency number.
+     * @hide
      */
-    public void notifyOutgoingEmergencyCall(int phoneId, int subId,
+    @SystemApi
+    public void notifyOutgoingEmergencyCall(int simSlotIndex, int subId,
             @NonNull EmergencyNumber emergencyNumber) {
         try {
-            sRegistry.notifyOutgoingEmergencyCall(phoneId, subId, emergencyNumber);
+            sRegistry.notifyOutgoingEmergencyCall(simSlotIndex, subId, emergencyNumber);
         } catch (RemoteException ex) {
             // system process is dead
             throw ex.rethrowFromSystemServer();
@@ -621,6 +647,7 @@
      * @param phoneId Sender phone ID.
      * @param subId Sender subscription ID.
      * @param emergencyNumber Emergency number.
+     * @hide
      */
     public void notifyOutgoingEmergencySms(int phoneId, int subId,
             @NonNull EmergencyNumber emergencyNumber) {
@@ -639,6 +666,7 @@
      * subId is invalid.
      * @param subId for which radio power state changed.
      * @param radioPowerState the current modem radio state.
+     * @hide
      */
     public void notifyRadioPowerStateChanged(int slotIndex, int subId,
             @RadioPowerState int radioPowerState) {
@@ -654,6 +682,7 @@
      * Notify {@link PhoneCapability} changed.
      *
      * @param phoneCapability the capability of the modem group.
+     * @hide
      */
     public void notifyPhoneCapabilityChanged(@NonNull PhoneCapability phoneCapability) {
         try {
@@ -685,6 +714,7 @@
      * when subId is invalid.
      * @param subId for which data activation state changed.
      * @param activationState sim activation state e.g, activated.
+     * @hide
      */
     public void notifyDataActivationStateChanged(int slotIndex, int subId,
             @SimActivationState int activationState) {
@@ -705,6 +735,7 @@
      * subId is invalid.
      * @param subId for which voice activation state changed.
      * @param activationState sim activation state e.g, activated.
+     * @hide
      */
     public void notifyVoiceActivationStateChanged(int slotIndex, int subId,
             @SimActivationState int activationState) {
@@ -725,6 +756,7 @@
      * when subId is invalid.
      * @param subId for which mobile data state has changed.
      * @param state {@code true} indicates mobile data is enabled/on. {@code false} otherwise.
+     * @hide
      */
     public void notifyUserMobileDataStateChanged(int slotIndex, int subId, boolean state) {
         try {
@@ -743,6 +775,7 @@
      * when the device is in emergency-only mode.
      * @param subscriptionId Subscription id for which display network info has changed.
      * @param telephonyDisplayInfo The display info.
+     * @hide
      */
     public void notifyDisplayInfoChanged(int slotIndex, int subscriptionId,
             @NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
@@ -759,6 +792,7 @@
      *
      * @param subId for which ims call disconnect.
      * @param imsReasonInfo the reason for ims call disconnect.
+     * @hide
      */
     public void notifyImsDisconnectCause(int subId, @NonNull ImsReasonInfo imsReasonInfo) {
         try {
@@ -775,6 +809,7 @@
      *
      * @param subId for which srvcc state changed.
      * @param state srvcc state
+     * @hide
      */
     public void notifySrvccStateChanged(int subId, @SrvccState int state) {
         try {
@@ -798,6 +833,7 @@
      * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
      *                        background calls.
      * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+     * @hide
      */
     public void notifyPreciseCallState(int slotIndex, int subId,
             @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
@@ -822,6 +858,7 @@
      * @param cause {@link DisconnectCause} for the disconnected call.
      * @param preciseCause {@link android.telephony.PreciseDisconnectCause} for the disconnected
      * call.
+     * @hide
      */
     public void notifyDisconnectCause(int slotIndex, int subId, @DisconnectCauses int cause,
             @PreciseDisconnectCauses int preciseCause) {
@@ -838,6 +875,7 @@
      *
      * <p>To be compatible with {@link TelephonyRegistry}, use {@link CellIdentity} which is
      * parcelable, and convert to CellLocation in client code.
+     * @hide
      */
     public void notifyCellLocation(int subId, @NonNull CellIdentity cellLocation) {
         try {
@@ -854,6 +892,7 @@
      *
      * @param subId for which cellinfo changed.
      * @param cellInfo A list of cellInfo associated with the given subscription.
+     * @hide
      */
     public void notifyCellInfoChanged(int subId, @NonNull List<CellInfo> cellInfo) {
         try {
@@ -866,6 +905,7 @@
     /**
      * Notify that the active data subscription ID has changed.
      * @param activeDataSubId The new subscription ID for active data
+     * @hide
      */
     public void notifyActiveDataSubIdChanged(int activeDataSubId) {
         try {
@@ -896,6 +936,7 @@
      *        For UMTS, if a combined attach succeeds for PS only, then the GMM cause code shall be
      *        included as an additionalCauseCode. For LTE (ESM), cause codes are in
      *        TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
+     * @hide
      */
     public void notifyRegistrationFailed(int slotIndex, int subId,
             @NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
@@ -914,6 +955,7 @@
      * @param slotIndex for the phone object that got updated barring info.
      * @param subId for which the BarringInfo changed.
      * @param barringInfo updated BarringInfo.
+     * @hide
      */
     public void notifyBarringInfoChanged(
             int slotIndex, int subId, @NonNull BarringInfo barringInfo) {
@@ -931,6 +973,7 @@
      * @param slotIndex for which physical channel configs changed.
      * @param subId the subId
      * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
+     * @hide
      */
     public void notifyPhysicalChannelConfigForSubscriber(int slotIndex, int subId,
             List<PhysicalChannelConfig> configs) {
@@ -948,6 +991,7 @@
      * @param enabled True if data is enabled, otherwise disabled.
      * @param reason Reason for data enabled/disabled. See {@code REASON_*} in
      * {@link TelephonyManager}.
+     * @hide
      */
     public void notifyDataEnabled(int slotIndex, int subId, boolean enabled,
             @TelephonyManager.DataEnabledReason int reason) {
@@ -966,6 +1010,7 @@
      * @param subId for which allowed network types changed.
      * @param reason an allowed network type reasons.
      * @param allowedNetworkType an allowed network type bitmask value.
+     * @hide
      */
     public void notifyAllowedNetworkTypesChanged(int slotIndex, int subId,
             int reason, long allowedNetworkType) {
@@ -983,6 +1028,7 @@
      * @param slotIndex for the phone object that gets the updated link capacity estimate
      * @param subId for subscription that gets the updated link capacity estimate
      * @param linkCapacityEstimateList a list of {@link  LinkCapacityEstimate}
+     * @hide
      */
     public void notifyLinkCapacityEstimateChanged(int slotIndex, int subId,
             List<LinkCapacityEstimate> linkCapacityEstimateList) {
@@ -998,8 +1044,10 @@
      * Notify external listeners that the subscriptions supporting simultaneous cellular calling
      * have changed.
      * @param subIds The new set of subIds supporting simultaneous cellular calling.
+     * @hide
      */
-    public void notifySimultaneousCellularCallingSubscriptionsChanged(Set<Integer> subIds) {
+    public void notifySimultaneousCellularCallingSubscriptionsChanged(
+            @NonNull Set<Integer> subIds) {
         try {
             sRegistry.notifySimultaneousCellularCallingSubscriptionsChanged(
                     subIds.stream().mapToInt(i -> i).toArray());
@@ -1009,6 +1057,12 @@
         }
     }
 
+    /**
+     * Processes potential event changes from the provided {@link TelephonyCallback}.
+     *
+     * @param telephonyCallback callback for monitoring callback changes to the telephony state.
+     * @hide
+     */
     public @NonNull Set<Integer> getEventsFromCallback(
             @NonNull TelephonyCallback telephonyCallback) {
         Set<Integer> eventList = new ArraySet<>();
@@ -1319,6 +1373,7 @@
      * may encounter an {@link IllegalStateException} when trying to register more callbacks.
      *
      * @param callback The {@link TelephonyCallback} object to register.
+     * @hide
      */
     public void registerTelephonyCallback(boolean renounceFineLocationAccess,
             boolean renounceCoarseLocationAccess,
@@ -1338,6 +1393,7 @@
      * Unregister an existing {@link TelephonyCallback}.
      *
      * @param callback The {@link TelephonyCallback} object to unregister.
+     * @hide
      */
     public void unregisterTelephonyCallback(int subId, String pkgName, String attributionTag,
             @NonNull TelephonyCallback callback, boolean notifyNow) {
@@ -1398,6 +1454,7 @@
      * @param logicalSlotIndex The SIM slot to listen on
      * @param executor The executor where {@code listener} will be invoked
      * @param callback The callback to register
+     * @hide
      */
     public void addCarrierPrivilegesCallback(
             int logicalSlotIndex,
@@ -1432,6 +1489,7 @@
      * Unregisters a {@link CarrierPrivilegesCallback}.
      *
      * @param callback The callback to unregister
+     * @hide
      */
     public void removeCarrierPrivilegesCallback(@NonNull CarrierPrivilegesCallback callback) {
         if (callback == null) {
@@ -1457,6 +1515,7 @@
      * @param logicalSlotIndex The SIM slot the change occurred on
      * @param privilegedPackageNames The updated set of packages names with carrier privileges
      * @param privilegedUids The updated set of UIDs with carrier privileges
+     * @hide
      */
     public void notifyCarrierPrivilegesChanged(
             int logicalSlotIndex,
@@ -1482,6 +1541,7 @@
      * @param logicalSlotIndex the SIM slot the change occurred on
      * @param packageName the package name of the changed {@link CarrierService}
      * @param uid the UID of the changed {@link CarrierService}
+     * @hide
      */
     public void notifyCarrierServiceChanged(int logicalSlotIndex, @Nullable String packageName,
             int uid) {
@@ -1498,6 +1558,7 @@
      *
      * @param executor The executor on which the callback will be executed.
      * @param listener The CarrierConfigChangeListener to be registered with.
+     * @hide
      */
     public void addCarrierConfigChangedListener(
             @NonNull @CallbackExecutor Executor executor,
@@ -1538,6 +1599,7 @@
      * Unregister to stop the notification when carrier configurations changed.
      *
      * @param listener The CarrierConfigChangeListener to be unregistered with.
+     * @hide
      */
     public void removeCarrierConfigChangedListener(
             @NonNull CarrierConfigManager.CarrierConfigChangeListener listener) {
@@ -1567,6 +1629,7 @@
      *                          {@link TelephonyManager#UNKNOWN_CARRIER_ID}.
      * @param specificCarrierId The optional specific carrier Id, may be {@link
      *                          TelephonyManager#UNKNOWN_CARRIER_ID}.
+     * @hide
      */
     public void notifyCarrierConfigChanged(int slotIndex, int subId, int carrierId,
             int specificCarrierId) {
@@ -1588,6 +1651,7 @@
      * @param subId Sender subscription ID.
      * @param type for callback mode entry.
      *             See {@link TelephonyManager.EmergencyCallbackModeType}.
+     * @hide
      */
     public void notifyCallBackModeStarted(int phoneId, int subId,
             @TelephonyManager.EmergencyCallbackModeType int type) {
@@ -1608,6 +1672,7 @@
      *             See {@link TelephonyManager.EmergencyCallbackModeType}.
      * @param reason for changing callback mode.
      *             See {@link TelephonyManager.EmergencyCallbackModeStopReason}.
+     * @hide
      */
     public void notifyCallbackModeStopped(int phoneId, int subId,
             @TelephonyManager.EmergencyCallbackModeType int type,
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index ac5eb3c..b268c2e 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -671,9 +671,18 @@
         if (mLtrWithoutBidi) {
             // If the whole text is LTR direction, just apply whole region.
             if (builder == null) {
-                mWholeWidth += paint.getTextRunAdvances(
-                        mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
-                        mWidths.getRawArray(), start);
+                // For the compatibility reasons, the letter spacing should not be dropped at the
+                // left and right edge.
+                int oldFlag = paint.getFlags();
+                paint.setFlags(paint.getFlags()
+                        | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+                try {
+                    mWholeWidth += paint.getTextRunAdvances(
+                            mCopiedBuffer, start, end - start, start, end - start,
+                            false /* isRtl */, mWidths.getRawArray(), start);
+                } finally {
+                    paint.setFlags(oldFlag);
+                }
             } else {
                 builder.appendStyleRun(paint, config, end - start, false /* isRtl */);
             }
@@ -690,9 +699,16 @@
                     final boolean isRtl = (level & 0x1) != 0;
                     if (builder == null) {
                         final int levelLength = levelEnd - levelStart;
-                        mWholeWidth += paint.getTextRunAdvances(
-                                mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
-                                isRtl, mWidths.getRawArray(), levelStart);
+                        int oldFlag = paint.getFlags();
+                        paint.setFlags(paint.getFlags()
+                                | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+                        try {
+                            mWholeWidth += paint.getTextRunAdvances(
+                                    mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
+                                    isRtl, mWidths.getRawArray(), levelStart);
+                        } finally {
+                            paint.setFlags(oldFlag);
+                        }
                     } else {
                         builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl);
                     }
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 135935c..2175b47 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -291,6 +291,97 @@
     }
 
     /**
+     * Returns the run flag of at the given BiDi run.
+     *
+     * @param bidiRunIndex a BiDi run index.
+     * @return a run flag of the given BiDi run.
+     */
+    @VisibleForTesting
+    public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) {
+        if (bidiRunCount == 1) {
+            // Easy case. If there is only single run, it is most left and most right run.
+            return Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+        }
+        if (bidiRunIndex != 0 && bidiRunIndex != (bidiRunCount - 1)) {
+            // Easy case. If the given run is the middle of the line, it is not the most left or
+            // the most right run.
+            return 0;
+        }
+
+        int runFlag = 0;
+        // For the historical reasons, the BiDi implementation of Android works differently
+        // from the Java BiDi APIs. The mDirections holds the BiDi runs in visual order, but
+        // it is reversed order if the paragraph direction is RTL. So, the first BiDi run of
+        // mDirections is located the most left of the line if the paragraph direction is LTR.
+        // If the paragraph direction is RTL, the first BiDi run is located the most right of
+        // the line.
+        if (bidiRunIndex == 0) {
+            if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+                runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+            } else {
+                runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+            }
+        }
+        if (bidiRunIndex == (bidiRunCount - 1)) {
+            if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+                runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+            } else {
+                runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+            }
+        }
+        return runFlag;
+    }
+
+    /**
+     * Resolve the runFlag for the inline span range.
+     *
+     * @param runFlag the runFlag of the current BiDi run.
+     * @param isRtlRun true for RTL run, false for LTR run.
+     * @param runStart the inclusive BiDi run start offset.
+     * @param runEnd the exclusive BiDi run end offset.
+     * @param spanStart the inclusive span start offset.
+     * @param spanEnd the exclusive span end offset.
+     * @return the resolved runFlag.
+     */
+    @VisibleForTesting
+    public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart,
+            int runEnd, int spanStart, int spanEnd) {
+        if (runFlag == 0) {
+            // Easy case. If the run is in the middle of the line, any inline span is also in the
+            // middle of the line.
+            return 0;
+        }
+        int localRunFlag = runFlag;
+        if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) != 0) {
+            if (isRtlRun) {
+                if (spanEnd != runEnd) {
+                    // In the RTL context, the last run is the most left run.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+                }
+            } else {  // LTR
+                if (spanStart != runStart) {
+                    // In the LTR context, the first run is the most left run.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+                }
+            }
+        }
+        if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) != 0) {
+            if (isRtlRun) {
+                if (spanStart != runStart) {
+                    // In the RTL context, the start of the run is the most right run.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+                }
+            } else {  // LTR
+                if (spanEnd != runEnd) {
+                    // In the LTR context, the last run is the most right position.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+                }
+            }
+        }
+        return localRunFlag;
+    }
+
+    /**
      * Renders the TextLine.
      *
      * @param c the canvas to render on
@@ -308,11 +399,13 @@
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                 if (j == runLimit || charAt(j) == TAB_CHAR) {
                     h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
-                            runIndex != (runCount - 1) || j != mLen);
+                            runIndex != (runCount - 1) || j != mLen, runFlag);
 
                     if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         h = mDir * nextTab(h * mDir);
@@ -371,11 +464,12 @@
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                 if (j == runLimit || charAt(j) == TAB_CHAR) {
                     horizontal += shapeRun(consumer, segStart, j, runIsRtl, x + horizontal,
-                            runIndex != (runCount - 1) || j != mLen);
+                            runIndex != (runCount - 1) || j != mLen, runFlag);
 
                     if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         horizontal = mDir * nextTab(horizontal * mDir);
@@ -441,11 +535,13 @@
         }
 
         float h = 0;
-        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
             if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
@@ -455,16 +551,16 @@
 
                     if (targetIsInThisSegment && sameDirection) {
                         return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
-                                0, h, lineInfo);
+                                0, h, lineInfo, runFlag);
                     }
 
                     final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
-                            null, 0, h, lineInfo);
+                            null, 0, h, lineInfo, runFlag);
                     h += sameDirection ? segmentWidth : -segmentWidth;
 
                     if (targetIsInThisSegment) {
                         return h + measureRun(segStart, offset, j, runIsRtl, null, null,  null, 0,
-                                h, lineInfo);
+                                h, lineInfo, runFlag);
                     }
 
                     if (j != runLimit) {  // charAt(j) == TAB_CHAR
@@ -543,20 +639,21 @@
                     + "result, needed: " + mLen + " had: " + advances.length);
         }
         float h = 0;
-        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
             if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                 if (j == runLimit || charAt(j) == TAB_CHAR) {
                     final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-
                     final float segmentWidth =
                             measureRun(segStart, j, j, runIsRtl, null, null, advances, segStart, 0,
-                                    null);
+                                    null, runFlag);
 
                     final float oldh = h;
                     h += sameDirection ? segmentWidth : -segmentWidth;
@@ -608,11 +705,13 @@
         }
 
         float horizontal = 0;
-        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
             if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
@@ -629,7 +728,7 @@
                     final float previousSegEndHorizontal = measurement[segStart];
                     final float width =
                             measureRun(segStart, j, j, runIsRtl, fmi, null, measurement, segStart,
-                                    0, null);
+                                    0, null, runFlag);
                     horizontal += sameDirection ? width : -width;
 
                     float currHorizontal = sameDirection ? oldHorizontal : horizontal;
@@ -686,22 +785,24 @@
      * @param y the baseline
      * @param bottom the bottom of the line
      * @param needWidth true if the width value is required.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run, based on the paragraph direction.
      * Only valid if needWidth is true.
      */
     private float drawRun(Canvas c, int start,
             int limit, boolean runIsRtl, float x, int top, int y, int bottom,
-            boolean needWidth) {
+            boolean needWidth, int runFlag) {
 
         if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
-            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null);
+            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+                    runFlag);
             handleRun(start, limit, limit, runIsRtl, c, null, x + w, top,
-                    y, bottom, null, null, false, null, 0, null);
+                    y, bottom, null, null, false, null, 0, null, runFlag);
             return w;
         }
 
         return handleRun(start, limit, limit, runIsRtl, c, null, x, top,
-                y, bottom, null, null, needWidth, null, 0, null);
+                y, bottom, null, null, needWidth, null, 0, null, runFlag);
     }
 
     /**
@@ -718,19 +819,21 @@
      * @param advancesIndex the start index to fill in the advance information.
      * @param x horizontal offset of the run.
      * @param lineInfo an optional output parameter for filling line information.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width from the start of the run to the leading edge
      * of the character at offset, based on the run (not paragraph) direction
      */
     private float measureRun(int start, int offset, int limit, boolean runIsRtl,
             @Nullable FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable float[] advances,
-            int advancesIndex, float x, @Nullable LineInfo lineInfo) {
+            int advancesIndex, float x, @Nullable LineInfo lineInfo, int runFlag) {
         if (drawBounds != null && (mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
-            float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null);
+            float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null,
+                    runFlag);
             return handleRun(start, offset, limit, runIsRtl, null, null, x + w, 0, 0, 0, fmi,
-                    drawBounds, true, advances, advancesIndex, lineInfo);
+                    drawBounds, true, advances, advancesIndex, lineInfo, runFlag);
         }
         return handleRun(start, offset, limit, runIsRtl, null, null, x, 0, 0, 0, fmi, drawBounds,
-                true, advances, advancesIndex, lineInfo);
+                true, advances, advancesIndex, lineInfo, runFlag);
     }
 
     /**
@@ -742,21 +845,23 @@
      * @param runIsRtl true if the run is right-to-left
      * @param x the position of the run that is closest to the leading margin
      * @param needWidth true if the width value is required.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run, based on the paragraph direction.
      * Only valid if needWidth is true.
      */
     private float shapeRun(TextShaper.GlyphsConsumer consumer, int start,
-            int limit, boolean runIsRtl, float x, boolean needWidth) {
+            int limit, boolean runIsRtl, float x, boolean needWidth, int runFlag) {
 
         if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
-            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null);
+            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+                    runFlag);
             handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, null,
-                    false, null, 0, null);
+                    false, null, 0, null, runFlag);
             return w;
         }
 
         return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null, null,
-                needWidth, null, 0, null);
+                needWidth, null, 0, null, runFlag);
     }
 
 
@@ -1160,6 +1265,7 @@
      * @param advances receives the advance information about the requested run, can be null.
      * @param advancesIndex the start index to fill in the advance information.
      * @param lineInfo an optional output parameter for filling line information.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run based on the run direction; only
      * valid if needWidth is true
      */
@@ -1168,8 +1274,8 @@
             Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom,
             FontMetricsInt fmi, RectF drawBounds, boolean needWidth, int offset,
             @Nullable ArrayList<DecorationInfo> decorations,
-            @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) {
-
+            @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+            int runFlag) {
         if (mIsJustifying) {
             wp.setWordSpacing(mAddedWidthForJustify);
         }
@@ -1187,7 +1293,16 @@
         }
 
         float totalWidth = 0;
-
+        if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) == Paint.TEXT_RUN_FLAG_LEFT_EDGE) {
+            wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+        } else {
+            wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+        }
+        if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) == Paint.TEXT_RUN_FLAG_RIGHT_EDGE) {
+            wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+        } else {
+            wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+        }
         final int numDecorations = decorations == null ? 0 : decorations.size();
         if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0
                 || numDecorations != 0 || runIsRtl))) {
@@ -1419,6 +1534,7 @@
      * @param advances receives the advance information about the requested run, can be null.
      * @param advancesIndex the start index to fill in the advance information.
      * @param lineInfo an optional output parameter for filling line information.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run based on the run direction; only
      * valid if needWidth is true
      */
@@ -1426,7 +1542,8 @@
             int limit, boolean runIsRtl, Canvas c,
             TextShaper.GlyphsConsumer consumer, float x, int top, int y,
             int bottom, FontMetricsInt fmi, RectF drawBounds, boolean needWidth,
-            @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) {
+            @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+            int runFlag) {
 
         if (measureLimit < start || measureLimit > limit) {
             throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
@@ -1473,7 +1590,7 @@
             wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
             return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top,
                     y, bottom, fmi, drawBounds, needWidth, measureLimit, null, advances,
-                    advancesIndex, lineInfo);
+                    advancesIndex, lineInfo, runFlag);
         }
 
         // Shaping needs to take into account context up to metric boundaries,
@@ -1554,6 +1671,9 @@
                     // and use.
                     activePaint.set(wp);
                 } else if (!equalAttributes(wp, activePaint)) {
+                    final int spanRunFlag = resolveRunFlagForSubSequence(
+                            runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
+
                     // The style of the present chunk of text is substantially different from the
                     // style of the previous chunk. We need to handle the active piece of text
                     // and restart with the present chunk.
@@ -1565,7 +1685,7 @@
                             consumer, x, top, y, bottom, fmi, drawBounds,
                             needWidth || activeEnd < measureLimit,
                             Math.min(activeEnd, mlimit), mDecorations,
-                            advances, advancesIndex + activeStart - start, lineInfo);
+                            advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
 
                     activeStart = j;
                     activePaint.set(wp);
@@ -1585,6 +1705,9 @@
                     mDecorations.add(copy);
                 }
             }
+
+            final int spanRunFlag = resolveRunFlagForSubSequence(
+                    runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
             // Handle the final piece of text.
             activePaint.setStartHyphenEdit(
                     adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
@@ -1593,7 +1716,7 @@
             x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x,
                     top, y, bottom, fmi, drawBounds, needWidth || activeEnd < measureLimit,
                     Math.min(activeEnd, mlimit), mDecorations,
-                    advances, advancesIndex + activeStart - start, lineInfo);
+                    advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
         }
 
         return x - originalX;
@@ -1614,7 +1737,6 @@
      */
     private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
             int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
-
         if (mCharsValid) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
diff --git a/core/java/android/tracing/transition/TransitionDataSource.java b/core/java/android/tracing/transition/TransitionDataSource.java
index b8daace..baece75 100644
--- a/core/java/android/tracing/transition/TransitionDataSource.java
+++ b/core/java/android/tracing/transition/TransitionDataSource.java
@@ -16,6 +16,7 @@
 
 package android.tracing.transition;
 
+import android.tracing.perfetto.CreateTlsStateArgs;
 import android.tracing.perfetto.DataSource;
 import android.tracing.perfetto.DataSourceInstance;
 import android.tracing.perfetto.FlushCallbackArguments;
@@ -23,10 +24,14 @@
 import android.tracing.perfetto.StopCallbackArguments;
 import android.util.proto.ProtoInputStream;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * @hide
  */
-public class TransitionDataSource extends DataSource {
+public class TransitionDataSource
+        extends DataSource<DataSourceInstance, TransitionDataSource.TlsState, Void> {
     public static String DATA_SOURCE_NAME = "com.android.wm.shell.transition";
 
     private final Runnable mOnStartStaticCallback;
@@ -41,6 +46,15 @@
     }
 
     @Override
+    protected TlsState createTlsState(CreateTlsStateArgs<DataSourceInstance> args) {
+        return new TlsState();
+    }
+
+    public class TlsState {
+        public final Map<String, Integer> handlerMapping = new HashMap<>();
+    }
+
+    @Override
     public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
         return new DataSourceInstance(this, instanceIndex) {
             @Override
diff --git a/core/java/android/view/EventLogTags.logtags b/core/java/android/view/EventLogTags.logtags
index cc0b18a..f1cd671 100644
--- a/core/java/android/view/EventLogTags.logtags
+++ b/core/java/android/view/EventLogTags.logtags
@@ -59,5 +59,10 @@
 # Enqueue Input Event
 62002 view_enqueue_input_event (eventType|3),(action|3)
 
+# following other view events defined in system/logging/logcat/event.logtags
+# ViewRoot Draw Events
+60004 viewroot_draw_event (window|3),(event|3)
+
+
 # NOTE - the range 1000000-2000000 is reserved for partners and others who
 # want to define their own log tags without conflicting with the core platform.
\ No newline at end of file
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 7850554..ba7874e 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -39,8 +39,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
+import android.view.flags.Flags;
 
 import dalvik.system.CloseGuard;
+import dalvik.system.VMRuntime;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -101,6 +103,10 @@
             long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
     private static native void nativeDestroy(long nativeObject);
 
+    // 5MB is a wild guess for what the average surface should be. On most new phones, a full-screen
+    // surface is about 9MB... but not all surfaces are screen size. This should be a nice balance.
+    private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000_000;
+
     public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
             new Parcelable.Creator<Surface>() {
         @Override
@@ -331,6 +337,7 @@
      */
     @UnsupportedAppUsage
     public Surface() {
+        registerNativeMemoryUsage();
     }
 
     /**
@@ -343,6 +350,7 @@
      */
     public Surface(@NonNull SurfaceControl from) {
         copyFrom(from);
+        registerNativeMemoryUsage();
     }
 
     /**
@@ -370,6 +378,7 @@
             mName = surfaceTexture.toString();
             setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
         }
+        registerNativeMemoryUsage();
     }
 
     /* called from android_view_Surface_createFromIGraphicBufferProducer() */
@@ -378,6 +387,7 @@
         synchronized (mLock) {
             setNativeObjectLocked(nativeObject);
         }
+        registerNativeMemoryUsage();
     }
 
     @Override
@@ -389,6 +399,7 @@
             release();
         } finally {
             super.finalize();
+            freeNativeMemoryUsage();
         }
     }
 
@@ -1243,4 +1254,16 @@
             return mIsWideColorGamut;
         }
     }
+
+    private static void registerNativeMemoryUsage() {
+        if (Flags.enableSurfaceNativeAllocRegistration()) {
+            VMRuntime.getRuntime().registerNativeAllocation(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
+        }
+    }
+
+    private static void freeNativeMemoryUsage() {
+        if (Flags.enableSurfaceNativeAllocRegistration()) {
+            VMRuntime.getRuntime().registerNativeFree(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
+        }
+    }
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 674f22c..3ed0385 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -294,8 +294,8 @@
     private static native void nativeClearTrustedPresentationCallback(long transactionObj,
             long nativeObject);
     private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
-    private static native void nativeSetDesiredPresentTime(long transactionObj,
-                                                           long desiredPresentTime);
+    private static native void nativeSetDesiredPresentTimeNanos(long transactionObj,
+                                                                long desiredPresentTimeNanos);
     private static native void nativeSetFrameTimeline(long transactionObj,
                                                            long vsyncId);
 
@@ -2563,12 +2563,12 @@
      */
     @FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
     public static final class TransactionStats {
-        private long mLatchTime;
+        private long mLatchTimeNanos;
         private SyncFence mSyncFence;
 
         // called from native
-        private TransactionStats(long latchTime, long presentFencePtr) {
-            mLatchTime = latchTime;
+        private TransactionStats(long latchTimeNanos, long presentFencePtr) {
+            mLatchTimeNanos = latchTimeNanos;
             mSyncFence = new SyncFence(presentFencePtr);
         }
 
@@ -2581,12 +2581,12 @@
         }
 
         /**
-         * Returns the timestamp of when the frame was latched by the framework and queued for
-         * presentation.
+         * Returns the timestamp (in CLOCK_MONOTONIC) of when the frame was latched by the
+         * framework and queued for presentation.
          */
         @FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
-        public long getLatchTime() {
-            return mLatchTime;
+        public long getLatchTimeNanos() {
+            return mLatchTimeNanos;
         }
 
         /**
@@ -4426,8 +4426,8 @@
         }
 
         /**
-         * Specifies a desiredPresentTime for the transaction. The framework will try to present
-         * the transaction at or after the time specified.
+         * Specifies a desiredPresentTimeNanos for the transaction. The framework will try to
+         * present the transaction at or after the time specified.
          *
          * Transactions will not be presented until all of their acquire fences have signaled even
          * if the app requests an earlier present time.
@@ -4436,17 +4436,17 @@
          * a desired present time that is before x, the later transaction will not preempt the
          * earlier transaction.
          *
-         * @param desiredPresentTime The desired time (in CLOCK_MONOTONIC) for the transaction.
+         * @param desiredPresentTimeNanos The desired time (in CLOCK_MONOTONIC) for the transaction.
          * @return This transaction
          */
         @FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
         @NonNull
-        public Transaction setDesiredPresentTime(long desiredPresentTime) {
+        public Transaction setDesiredPresentTimeNanos(long desiredPresentTimeNanos) {
             if (!Flags.sdkDesiredPresentTime()) {
                 Log.w(TAG, "addTransactionCompletedListener was called but flag is disabled");
                 return this;
             }
-            nativeSetDesiredPresentTime(mNativeObject, desiredPresentTime);
+            nativeSetDesiredPresentTimeNanos(mNativeObject, desiredPresentTimeNanos);
             return this;
         }
         /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 34b46700..e0bda91 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -26,6 +26,7 @@
 import static android.view.InputDevice.SOURCE_CLASS_NONE;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -286,6 +287,7 @@
     private static final boolean DEBUG_TOUCH_NAVIGATION = false || LOCAL_LOGV;
     private static final boolean DEBUG_BLAST = false || LOCAL_LOGV;
     private static final int LOGTAG_INPUT_FOCUS = 62001;
+    private static final int LOGTAG_VIEWROOT_DRAW_EVENT = 60004;
 
     /**
      * Set to false if we do not want to use the multi threaded renderer even though
@@ -1010,8 +1012,10 @@
     // Used to check if there were any view invalidations in
     // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
     private boolean mHasInvalidation = false;
-    // Used to check if it is in the touch boosting period.
+    // Used to check if it is in the frame rate boosting period.
     private boolean mIsFrameRateBoosting = false;
+    // Used to check if it is in touch boosting period.
+    private boolean mIsTouchBoosting = false;
     // Used to check if there is a message in the message queue
     // for idleness handling.
     private boolean mHasIdledMessage = false;
@@ -4750,13 +4754,7 @@
     }
 
     private void reportDrawFinished(@Nullable Transaction t, int seqId) {
-        if (DEBUG_BLAST) {
-            Log.d(mTag, "reportDrawFinished");
-        }
-
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.instant(Trace.TRACE_TAG_VIEW, "reportDrawFinished " + mTag + " seqId=" + seqId);
-        }
+        logAndTrace("reportDrawFinished seqId=" + seqId);
         try {
             mWindowSession.finishDrawing(mWindow, t, seqId);
         } catch (RemoteException e) {
@@ -6426,11 +6424,12 @@
                      * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
                      */
                     mIsFrameRateBoosting = false;
+                    mIsTouchBoosting = false;
                     setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
                             mLastPreferredFrameRateCategory));
                     break;
                 case MSG_CHECK_INVALIDATION_IDLE:
-                    if (!mHasInvalidation && !mIsFrameRateBoosting) {
+                    if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
                         mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
                         setPreferredFrameRateCategory(mPreferredFrameRateCategory);
                         mHasIdledMessage = false;
@@ -7453,7 +7452,7 @@
             // For the variable refresh rate project
             if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
                 // set the frame rate to the maximum value.
-                mIsFrameRateBoosting = true;
+                mIsTouchBoosting = true;
                 setPreferredFrameRateCategory(mPreferredFrameRateCategory);
             }
             /**
@@ -12194,7 +12193,10 @@
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.instant(Trace.TRACE_TAG_VIEW, mTag + "-" + msg);
         }
-        Log.d(mTag, msg);
+        if (DEBUG_BLAST) {
+            Log.d(mTag, msg);
+        }
+        EventLog.writeEvent(LOGTAG_VIEWROOT_DRAW_EVENT, mTag, msg);
     }
 
     private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
@@ -12202,8 +12204,16 @@
             return;
         }
 
-        int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning
-                ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
+        int frameRateCategory = mIsTouchBoosting
+                ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
+
+        // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
+        // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
+        // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
+        // (e.g., Window Initialization).
+        if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
+            frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+        }
 
         try {
             if (mLastPreferredFrameRateCategory != frameRateCategory) {
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index ad0e9a4..f67eefa 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -8,6 +8,13 @@
 }
 
 flag {
+    name: "bal_require_opt_in_same_uid"
+    namespace: "responsible_apis"
+    description: "Require the PendingIntent creator/sender to opt in if it is the same UID"
+    bug: "296478951"
+}
+
+flag {
     name: "bal_dont_bring_existing_background_task_stack_to_fg"
     namespace: "responsible_apis"
     description: "When starting a PendingIntent with ONLY creator privileges, don't bring the existing task stack to foreground"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 2b96ee6..2c5fbd7 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -89,4 +89,12 @@
     description: "Feature flag to enable a multi-instance system ui component property."
     bug: "262864589"
     is_fixed_read_only: true
+}
+
+flag {
+  name: "insets_decoupled_configuration"
+  namespace: "windowing_frontend"
+  description: "Configuration decoupled from insets"
+  bug: "151861875"
+  is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f743ab7..6e5807b 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -46,4 +46,11 @@
     description: "To dispatch ActivityWindowInfo through ClientTransaction"
     bug: "287582673"
     is_fixed_read_only: true
+}
+
+flag {
+    namespace: "windowing_sdk"
+    name: "embedded_activity_back_nav_flag"
+    description: "Refines embedded activity back navigation behavior"
+    bug: "240575809"
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
index c3bcfa6..eca6f58 100644
--- a/core/java/com/android/internal/os/MonotonicClock.java
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -17,11 +17,11 @@
 package com.android.internal.os;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Xml;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
@@ -49,20 +49,27 @@
 
     private final AtomicFile mFile;
     private final Clock mClock;
-    private long mTimeshift;
+    private final long mTimeshift;
 
     public static final long UNDEFINED = -1;
 
     public MonotonicClock(File file) {
-        mFile = new AtomicFile(file);
-        mClock = Clock.SYSTEM_CLOCK;
-        read();
+        this (file, Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK);
     }
 
     public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+        this(null, monotonicTime, clock);
+    }
+
+    public MonotonicClock(@Nullable File file, long monotonicTime, @NonNull Clock clock) {
         mClock = clock;
-        mFile = null;
-        mTimeshift = monotonicTime - mClock.elapsedRealtime();
+        if (file != null) {
+            mFile = new AtomicFile(file);
+            mTimeshift = read(monotonicTime - mClock.elapsedRealtime());
+        } else {
+            mFile = null;
+            mTimeshift = monotonicTime - mClock.elapsedRealtime();
+        }
     }
 
     /**
@@ -81,15 +88,16 @@
         return mTimeshift + elapsedRealtimeMs;
     }
 
-    private void read() {
+    private long read(long defaultTimeshift) {
         if (!mFile.exists()) {
-            return;
+            return defaultTimeshift;
         }
 
         try {
-            readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+            return readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
         } catch (IOException e) {
             Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+            return defaultTimeshift;
         }
     }
 
@@ -102,18 +110,21 @@
             return;
         }
 
-        try (FileOutputStream out = mFile.startWrite()) {
+        FileOutputStream out = null;
+        try  {
+            out = mFile.startWrite();
             writeXml(out, Xml.newBinarySerializer());
+            mFile.finishWrite(out);
         } catch (IOException e) {
             Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+            mFile.failWrite(out);
         }
     }
 
     /**
      * Parses an XML file containing the persistent state of the monotonic clock.
      */
-    @VisibleForTesting
-    public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+    private long readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
         long savedTimeshift = 0;
         try {
             parser.setInput(inputStream, StandardCharsets.UTF_8.name());
@@ -128,14 +139,13 @@
         } catch (XmlPullParserException e) {
             throw new IOException(e);
         }
-        mTimeshift = savedTimeshift - mClock.elapsedRealtime();
+        return savedTimeshift - mClock.elapsedRealtime();
     }
 
     /**
      * Creates an XML file containing the persistent state of the monotonic clock.
      */
-    @VisibleForTesting
-    public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+    private void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
         serializer.setOutput(out, StandardCharsets.UTF_8.name());
         serializer.startDocument(null, true);
         serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
index e3bfb38..e419d13 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
@@ -38,7 +38,7 @@
 public class ParsedAttributionImpl implements ParsedAttribution, Parcelable {
 
     /** Maximum amount of attributions per package */
-    static final int MAX_NUM_ATTRIBUTIONS = 10000;
+    static final int MAX_NUM_ATTRIBUTIONS = 400;
 
     /** Tag of the attribution */
     private @NonNull String tag;
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index b87027c..4191936 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -120,8 +120,9 @@
     public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
             Function<? super I, ? extends O> f) {
         if (isEmpty(cur)) return Collections.emptyList();
-        final ArrayList<O> result = new ArrayList<>();
-        for (int i = 0; i < cur.size(); i++) {
+        final int size = cur.size();
+        final ArrayList<O> result = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
             result.add(f.apply(cur.get(i)));
         }
         return result;
@@ -133,7 +134,7 @@
     public static @NonNull <I, O> Set<O> map(@Nullable Set<I> cur,
             Function<? super I, ? extends O> f) {
         if (isEmpty(cur)) return emptySet();
-        ArraySet<O> result = new ArraySet<>();
+        ArraySet<O> result = new ArraySet<>(cur.size());
         if (cur instanceof ArraySet) {
             ArraySet<I> arraySet = (ArraySet<I>) cur;
             int size = arraySet.size();
@@ -163,7 +164,7 @@
             Function<? super I, ? extends O> f) {
         if (isEmpty(cur)) return Collections.emptyList();
         List<O> result = null;
-        for (int i = 0; i < cur.size(); i++) {
+        for (int i = 0, size = cur.size(); i < size; i++) {
             O transformed = f.apply(cur.get(i));
             if (transformed != null) {
                 result = add(result, transformed);
@@ -239,7 +240,7 @@
     public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
         if (isEmpty(list)) return Collections.emptyList();
         ArrayList<T> result = null;
-        for (int i = 0; i < list.size(); i++) {
+        for (int i = 0, size = list.size(); i < size; i++) {
             final Object item = list.get(i);
             if (c.isInstance(item)) {
                 result = ArrayUtils.add(result, (T) item);
@@ -273,7 +274,7 @@
     public static @Nullable <T> T find(@Nullable List<T> items,
             java.util.function.Predicate<T> predicate) {
         if (isEmpty(items)) return null;
-        for (int i = 0; i < items.size(); i++) {
+        for (int i = 0, size = items.size(); i < size; i++) {
             final T item = items.get(i);
             if (predicate.test(item)) return item;
         }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index bf8e613..757978b 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1384,8 +1384,8 @@
     }
 
     public boolean isUserInLockdown(int userId) {
-        return getStrongAuthForUser(userId)
-                == StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+        return (getStrongAuthForUser(userId)
+                & StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN) != 0;
     }
 
     private static class WrappedCallback extends ICheckCredentialProgressCallback.Stub {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 55326da..25b2aaf 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1938,11 +1938,11 @@
     transaction->setFrameTimelineInfo(ftInfo);
 }
 
-static void nativeSetDesiredPresentTime(JNIEnv* env, jclass clazz, jlong transactionObj,
-                                        jlong desiredPresentTime) {
+static void nativeSetDesiredPresentTimeNanos(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                             jlong desiredPresentTimeNanos) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
 
-    transaction->setDesiredPresentTime(desiredPresentTime);
+    transaction->setDesiredPresentTime(desiredPresentTimeNanos);
 }
 
 static void nativeAddTransactionCommittedListener(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -2412,8 +2412,8 @@
     {"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
     {"nativeGetStalledTransactionInfo", "(I)Landroid/gui/StalledTransactionInfo;",
             (void*) nativeGetStalledTransactionInfo },
-    {"nativeSetDesiredPresentTime", "(JJ)V",
-            (void*) nativeSetDesiredPresentTime },
+    {"nativeSetDesiredPresentTimeNanos", "(JJ)V",
+            (void*) nativeSetDesiredPresentTimeNanos },
         // clang-format on
 };
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4a82675..adea800 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3042,6 +3042,17 @@
     <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
         android:protectionLevel="internal|role" />
 
+    <!-- Used to provide the Telecom framework with access to the last known call ID.
+         <p>Protection level: signature
+         @SystemApi
+         @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
+         @hide
+    -->
+    <permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID"
+        android:protectionLevel="signature"
+        android:label="@string/permlab_accessLastKnownCellId"
+        android:description="@string/permdesc_accessLastKnownCellId"/>
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
@@ -3160,7 +3171,7 @@
          types of interactions
          @hide -->
     <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
-        android:protectionLevel="signature|installer|role" />
+        android:protectionLevel="signature|installer|module|role" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 
     <!-- Allows interaction across profiles in the same profile group. -->
@@ -5055,7 +5066,7 @@
          <p>Intended for use by ROLE_ASSISTANT and signature apps only.
     -->
     <permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
-                android:protectionLevel="signature|role"/>
+                android:protectionLevel="signature|module|role"/>
 
     <!-- Must be required by a {@link android.service.autofill.AutofillService},
          to ensure that only the system can bind to it.
@@ -5673,7 +5684,7 @@
     <!-- @SystemApi Allows an application to manage the holders of a role.
          @hide -->
     <permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
-                android:protectionLevel="signature|installer" />
+                android:protectionLevel="signature|installer|module" />
 
     <!-- @SystemApi Allows an application to manage the holders of roles associated with default
          applications.
@@ -5693,7 +5704,7 @@
     <!-- @SystemApi Allows an application to observe role holder changes.
          @hide -->
     <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
-                android:protectionLevel="signature|installer" />
+                android:protectionLevel="signature|installer|module" />
 
     <!-- Allows an application to manage the companion devices.
          @hide -->
@@ -7908,6 +7919,14 @@
     <permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES"
         android:protectionLevel="signature|role" />
 
+    <!-- @SystemApi Allows an application to read the system grammatical gender.
+         @FlaggedApi("android.app.system_terms_of_address_enabled")
+         <p>Protection level: signature|privileged|appop
+         @hide
+    -->
+    <permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"
+                android:protectionLevel="signature|privileged|appop"/>
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index f3acab0..fe12f6e 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2021 The Android Open Source Project
+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.
@@ -20,179 +20,103 @@
     android:viewportWidth="512"
     android:viewportHeight="512">
   <path
-      android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
+      android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z"
+      android:strokeWidth="0">
     <aapt:attr name="android:fillColor">
       <gradient 
-          android:startX="256"
-          android:startY="21.81"
-          android:endX="256"
-          android:endY="350.42"
+          android:startX="56.22"
+          android:startY="256"
+          android:endX="456.22"
+          android:endY="256"
           android:type="linear">
         <item android:offset="0" android:color="#FF073042"/>
         <item android:offset="1" android:color="#FF073042"/>
       </gradient>
     </aapt:attr>
   </path>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
-        android:fillColor="#3ddc84"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
-        android:fillColor="#3ddc84"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
-        android:fillColor="#3ddc84"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M378.92,192h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
   <path
-      android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
-      android:strokeWidth="56.561"
-      android:fillColor="#00000000"
-      android:strokeColor="#f86734"/>
-  <path
-      android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
+      android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z"
+      android:strokeWidth="0"
       android:fillColor="#3ddc84"/>
   <path
-      android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
+      android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
+      android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z"
+      android:strokeWidth="0"
+      android:fillColor="#3ddc84"/>
+  <path
+      android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z"
+      android:strokeWidth="0"
+      android:fillColor="#3ddc84"/>
+  <path
+      android:pathData="M159.03,176.91h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
+      android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
+      android:pathData="M373.41,158.93h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
+      android:pathData="M112.1,129.34h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
+      android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
+      android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
+      android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
+      android:pathData="M330.82,263.31h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
+  <path
+      android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M224.18,216.82h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M293.34,339.25h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M165.09,236.28h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M378.92,192h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M204.28,314.86h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z"
+      android:strokeWidth="0"
+      android:fillColor="#3ddc84"/>
+  <path
+      android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z"
+      android:strokeWidth="55"
+      android:fillColor="#00000000"
+      android:strokeColor="#f86733"/>
 </vector>
-
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5cfb1a3..2eb28eb 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4372,6 +4372,14 @@
         <attr name="name" />
     </declare-styleable>
 
+    <!-- Specify one or more <code>polling-loop-filter</code> elements inside a
+         <code>host-apdu-service</code> to indicate polling loop frames that
+         your service can handle. -->
+    <declare-styleable name="PollingLoopFilter">
+        <!-- The polling loop frame. This attribute is mandatory. -->
+        <attr name="name" />
+    </declare-styleable>
+
     <!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that
          describes an {@link android.nfc.cardemulation.HostNfcFService} service, which
          is referenced from its {@link android.nfc.cardemulation.HostNfcFService#SERVICE_META_DATA}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8fae6db..29086a45 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1813,6 +1813,12 @@
 
     <attr name="allowUpdateOwnership" format="boolean" />
 
+    <!-- This attribute can be applied to any tag in the manifest. The system uses its value to
+         determine whether the element (e.g., a permission) should be enabled or disabled
+         depending on the state of feature flags.
+         @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
+    <attr name="featureFlag" format="string" />
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 5346454..d0216b3 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -192,8 +192,13 @@
 
     <!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC
          of the satellite PLMN with the format "mccmnc". -->
-    <string name="config_satellite_sim_identifier" translatable="false"></string>
-    <java-symbol type="string" name="config_satellite_sim_identifier" />
+    <string name="config_satellite_sim_plmn_identifier" translatable="false"></string>
+    <java-symbol type="string" name="config_satellite_sim_plmn_identifier" />
+
+    <!-- The identifier for the satellite's SIM profile. The identifier is the service provider name
+    (spn) from the profile metadata. -->
+    <string name="config_satellite_sim_spn_identifier" translatable="false"></string>
+    <java-symbol type="string" name="config_satellite_sim_spn_identifier" />
 
     <!-- The app to which the emergency call will be handed over for OEM-enabled satellite
          messaging. The format of the config string is "package_name;class_name". -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 53b473e..7d22885 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -119,6 +119,8 @@
     <public name="optional"/>
     <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
     <public name="adServiceTypes" />
+    <!-- @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
+    <public name="featureFlag"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f285806..bd3a5e4 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1075,6 +1075,12 @@
     <string name="permdesc_manageOngoingCalls">Allows an app to see details about ongoing calls
          on your device and to control these calls.</string>
 
+    <!-- Title for an application permission, listed so that the user can access the last known cell id provided by telephony. -->
+    <string name="permlab_accessLastKnownCellId">Access last known cell identity.</string>
+    <!-- Description on an application permission, listed so that the user can access the last known cell id provided by telephony -->
+    <string name="permdesc_accessLastKnownCellId">Allows an app to access to the last known
+        cell identity provided by telephony.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readCellBroadcasts">read cell broadcast messages</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
index b1cf9c2..bb69fa4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
@@ -124,6 +124,17 @@
     }
 
     @Test
+    public void construct_withNullInSecondaryIdsForSelector() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            new ProgramSelector(DAB_PROGRAM_TYPE, DAB_DMB_SID_EXT_IDENTIFIER_1,
+                    new ProgramSelector.Identifier[]{null}, /* vendorIds= */ null);
+        });
+
+        assertWithMessage("Exception for null secondary id")
+                .that(thrown).hasMessageThat().contains("secondaryIds list must not contain nulls");
+    }
+
+    @Test
     public void getProgramType() {
         ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
 
@@ -269,11 +280,11 @@
 
         ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY);
 
-        assertWithMessage("Program type")
+        assertWithMessage("AM program type without subchannel")
                 .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
-        assertWithMessage("Primary identifiers")
+        assertWithMessage("AM primary identifiers without subchannel")
                 .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
-        assertWithMessage("Secondary identifiers")
+        assertWithMessage("AM secondary identifiers without subchannel")
                 .that(selector.getSecondaryIds()).isEmpty();
     }
 
@@ -285,15 +296,29 @@
         ProgramSelector selector = ProgramSelector.createAmFmSelector(
                 RadioManager.BAND_INVALID, (int) FM_FREQUENCY);
 
-        assertWithMessage("Program type")
+        assertWithMessage("FM program type without band and subchannel")
                 .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_FM);
-        assertWithMessage("Primary identifiers")
+        assertWithMessage("FM primary identifiers without band and subchannel")
                 .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
-        assertWithMessage("Secondary identifiers")
+        assertWithMessage("FM secondary identifiers without band and subchannel")
                 .that(selector.getSecondaryIds()).isEmpty();
     }
 
     @Test
+    public void createAmFmSelector_withValidFrequency() {
+        ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+                ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, AM_FREQUENCY);
+
+        ProgramSelector selector = ProgramSelector.createAmFmSelector(RadioManager.BAND_INVALID,
+                (int) AM_FREQUENCY);
+
+        assertWithMessage("AM program type with valid frequency")
+                .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
+        assertWithMessage("AM primary identifiers with valid frequency")
+                .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+    }
+
+    @Test
     public void createAmFmSelector_withValidFrequencyAndSubChannel() {
         int band = RadioManager.BAND_AM_HD;
         int subChannel = 2;
@@ -307,15 +332,26 @@
         ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY,
                 subChannel);
 
-        assertWithMessage("Program type")
+        assertWithMessage("AM program type with valid frequency and subchannel")
                 .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
-        assertWithMessage("Primary identifiers")
+        assertWithMessage("AM primary identifiers with valid frequency and subchannel")
                 .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
-        assertWithMessage("Secondary identifiers")
+        assertWithMessage("AM secondary identifiers with valid frequency and subchannel")
                 .that(selector.getSecondaryIds()).isEqualTo(secondaryIdExpected);
     }
 
     @Test
+    public void createAmFmSelector_withInvalidBand_throwsIllegalArgumentException() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            ProgramSelector.createAmFmSelector(/* band= */ 1000, (int) AM_FREQUENCY);
+        });
+
+        assertWithMessage("Exception for using invalid band")
+                .that(thrown).hasMessageThat().contains(
+                        "Unknown band");
+    }
+
+    @Test
     public void createAmFmSelector_withInvalidFrequency_throwsIllegalArgumentException() {
         int invalidFrequency = 50000;
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index 89464d1..bbac69f 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -169,6 +169,16 @@
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Test
+    public void constructor_withUnsupportedTypeForBandDescriptor_throwsException() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                () -> new RadioManager.AmBandDescriptor(REGION, /* type= */ 100, AM_LOWER_LIMIT,
+                        AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED));
+
+        assertWithMessage("Unsupported band type exception")
+                .that(thrown).hasMessageThat().contains("Unsupported band");
+    }
+
+    @Test
     public void getType_forBandDescriptor() {
         RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
 
@@ -363,6 +373,18 @@
     }
 
     @Test
+    public void equals_withAmBandDescriptorsAndOtherTypeObject() {
+        assertWithMessage("AM Band Descriptor")
+                .that(AM_BAND_DESCRIPTOR).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsAndOtherTypeObject() {
+        assertWithMessage("FM Band Descriptor")
+                .that(FM_BAND_DESCRIPTOR).isNotEqualTo(AM_BAND_DESCRIPTOR);
+    }
+
+    @Test
     public void equals_withAmBandDescriptorsOfDifferentUpperLimits_returnsFalse() {
         RadioManager.AmBandDescriptor amBandDescriptorCompared =
                 new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
@@ -373,9 +395,83 @@
     }
 
     @Test
-    public void equals_withAmAndFmBandDescriptors_returnsFalse() {
-        assertWithMessage("AM Band Descriptor")
-                .that(AM_BAND_DESCRIPTOR).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    public void equals_withAmBandDescriptorsOfDifferentStereoSupportValues() {
+        RadioManager.AmBandDescriptor amBandDescriptorCompared =
+                new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
+                        AM_UPPER_LIMIT, AM_SPACING, !STEREO_SUPPORTED);
+
+        assertWithMessage("AM Band Descriptor of different stereo support values")
+                .that(AM_BAND_DESCRIPTOR).isNotEqualTo(amBandDescriptorCompared);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentSpacingValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING * 2,
+                STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different support limit values")
+                .that(FM_BAND_DESCRIPTOR).isNotEqualTo(fmBandDescriptorCompared);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsOfDifferentLowerLimitValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION + 1, RadioManager.BAND_AM_HD, AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING,
+                STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different region values")
+                .that(FM_BAND_DESCRIPTOR).isNotEqualTo(fmBandDescriptorCompared);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentStereoSupportValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                !STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different stereo support values")
+                .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentRdsSupportValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                STEREO_SUPPORTED, !RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different rds support values")
+                .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentTaSupportValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                STEREO_SUPPORTED, RDS_SUPPORTED, !TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different ta support values")
+                .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentAfSupportValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, !AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different af support values")
+                .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentEaSupportValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, !EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different ea support values")
+                .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
     }
 
     @Test
@@ -587,18 +683,79 @@
     }
 
     @Test
-    public void equals_withFmBandConfigsOfDifferentAfs_returnsFalse() {
-        RadioManager.FmBandConfig.Builder builder = new RadioManager.FmBandConfig.Builder(
-                createFmBandDescriptor()).setStereo(STEREO_SUPPORTED).setRds(RDS_SUPPORTED)
-                .setTa(TA_SUPPORTED).setAf(!AF_SUPPORTED).setEa(EA_SUPPORTED);
-        RadioManager.FmBandConfig fmBandConfigFromBuilder = builder.build();
+    public void equals_withFmBandConfigsOfDifferentRegionValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION + 1, RadioManager.BAND_AM_HD,
+                        AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED,
+                        TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED));
 
-        assertWithMessage("FM Band Config of different af value")
-                .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigFromBuilder);
+        assertWithMessage("FM Band Config of different regions")
+                .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigCompared);
     }
 
     @Test
-    public void equals_withFmAndAmBandConfigs_returnsFalse() {
+    public void equals_withFmBandConfigsOfDifferentStereoSupportValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+                        FM_UPPER_LIMIT, FM_SPACING, !STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+                        AF_SUPPORTED, EA_SUPPORTED));
+
+        assertWithMessage("FM Band Config with different stereo support values")
+                .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsOfDifferentRdsSupportValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+                        FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, !RDS_SUPPORTED, TA_SUPPORTED,
+                        AF_SUPPORTED, EA_SUPPORTED));
+
+        assertWithMessage("FM Band Config with different RDS support values")
+                .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsOfDifferentTaSupportValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+                        FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, !TA_SUPPORTED,
+                        AF_SUPPORTED, EA_SUPPORTED));
+
+        assertWithMessage("FM Band Configs with different ta values")
+                .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsOfDifferentAfSupportValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+                        FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+                        !AF_SUPPORTED, EA_SUPPORTED));
+
+        assertWithMessage("FM Band Config of different af support value")
+                .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigCompared);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsOfDifferentEaSupportValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+                        FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+                        AF_SUPPORTED, !EA_SUPPORTED));
+
+        assertWithMessage("FM Band Configs with different ea support values")
+                .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void equals_withAmBandConfigsAndOtherTypeObject() {
+        assertWithMessage("AM Band Config")
+                .that(AM_BAND_CONFIG).isNotEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsAndOtherTypeObject() {
         assertWithMessage("FM Band Config")
                 .that(FM_BAND_CONFIG).isNotEqualTo(AM_BAND_CONFIG);
     }
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
index 7b9121e..fddfd39 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
@@ -16,14 +16,20 @@
 
 package android.hardware.radio;
 
-import static com.google.common.truth.Truth.assertWithMessage;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
 
 import android.graphics.Bitmap;
 import android.os.Parcel;
 import android.platform.test.flag.junit.SetFlagsRule;
 
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+
+import com.google.common.truth.Expect;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,7 +40,7 @@
 import java.util.Set;
 
 @RunWith(MockitoJUnitRunner.class)
-public final class RadioMetadataTest {
+public final class RadioMetadataTest extends ExtendedRadioMockitoTestCase {
 
     private static final int CREATOR_ARRAY_SIZE = 3;
     private static final int INT_KEY_VALUE = 1;
@@ -49,14 +55,21 @@
     private Bitmap mBitmapValue;
 
     @Rule
+    public final Expect mExpect = Expect.create();
+    @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(Bitmap.class);
+    }
+
     @Test
     public void describeContents_forClock() {
         RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH,
                 TEST_TIME_ZONE_OFFSET_MINUTES);
 
-        assertWithMessage("Describe contents for metadata clock")
+        mExpect.withMessage("Describe contents for metadata clock")
                 .that(clock.describeContents()).isEqualTo(0);
     }
 
@@ -64,7 +77,7 @@
     public void newArray_forClockCreator() {
         RadioMetadata.Clock[] clocks = RadioMetadata.Clock.CREATOR.newArray(CREATOR_ARRAY_SIZE);
 
-        assertWithMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE);
+        mExpect.withMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE);
     }
 
     @Test
@@ -77,9 +90,9 @@
         parcel.setDataPosition(0);
 
         RadioMetadata.Clock clockFromParcel = RadioMetadata.Clock.CREATOR.createFromParcel(parcel);
-        assertWithMessage("UTC second since epoch of metadata clock created from parcel")
+        mExpect.withMessage("UTC second since epoch of metadata clock created from parcel")
                 .that(clockFromParcel.getUtcEpochSeconds()).isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
-        assertWithMessage("Time zone offset minutes of metadata clock created from parcel")
+        mExpect.withMessage("Time zone offset minutes of metadata clock created from parcel")
                 .that(clockFromParcel.getTimezoneOffsetMinutes())
                 .isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
     }
@@ -92,7 +105,7 @@
             mBuilder.putString(invalidStringKey, "value");
         });
 
-        assertWithMessage("Exception for putting illegal string-value key %s", invalidStringKey)
+        mExpect.withMessage("Exception for putting illegal string-value key %s", invalidStringKey)
                 .that(thrown).hasMessageThat()
                 .matches(".*" + invalidStringKey + ".*cannot.*String.*?");
     }
@@ -105,12 +118,25 @@
             mBuilder.putInt(invalidIntKey, INT_KEY_VALUE);
         });
 
-        assertWithMessage("Exception for putting illegal int-value for key %s", invalidIntKey)
+        mExpect.withMessage("Exception for putting illegal int-value for key %s", invalidIntKey)
                 .that(thrown).hasMessageThat()
                 .matches(".*" + invalidIntKey + ".*cannot.*int.*?");
     }
 
     @Test
+    public void putBitmap_withIllegalKey() {
+        String invalidIntKey = RadioMetadata.METADATA_KEY_GENRE;
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            mBuilder.putBitmap(invalidIntKey, mBitmapValue);
+        });
+
+        mExpect.withMessage("Exception for putting illegal bitmap-value for key %s", invalidIntKey)
+                .that(thrown).hasMessageThat()
+                .matches(".*" + invalidIntKey + ".*cannot.*Bitmap.*?");
+    }
+
+    @Test
     public void putClock_withIllegalKey() {
         String invalidClockKey = RadioMetadata.METADATA_KEY_ALBUM;
 
@@ -119,7 +145,7 @@
                     /* timezoneOffsetMinutes= */ 1);
         });
 
-        assertWithMessage("Exception for putting illegal clock-value key %s", invalidClockKey)
+        mExpect.withMessage("Exception for putting illegal clock-value key %s", invalidClockKey)
                 .that(thrown).hasMessageThat()
                 .matches(".*" + invalidClockKey + ".*cannot.*Clock.*?");
     }
@@ -133,7 +159,7 @@
             mBuilder.putStringArray(invalidStringArrayKey, UFIDS_VALUE);
         });
 
-        assertWithMessage("Exception for putting illegal string-array-value for key %s",
+        mExpect.withMessage("Exception for putting illegal string-array-value for key %s",
                 invalidStringArrayKey).that(thrown).hasMessageThat()
                 .matches(".*" + invalidStringArrayKey + ".*cannot.*Array.*?");
     }
@@ -146,7 +172,7 @@
             mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE);
         });
 
-        assertWithMessage("Exception for putting string-array with null key")
+        mExpect.withMessage("Exception for putting string-array with null key")
                 .that(thrown).hasMessageThat().contains("can not be null");
     }
 
@@ -158,7 +184,7 @@
             mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null);
         });
 
-        assertWithMessage("Exception for putting null string-array")
+        mExpect.withMessage("Exception for putting null string-array")
                 .that(thrown).hasMessageThat().contains("can not be null");
     }
 
@@ -167,7 +193,7 @@
         String key = RadioMetadata.METADATA_KEY_RDS_PI;
         RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
 
-        assertWithMessage("Whether metadata contains %s in metadata", key)
+        mExpect.withMessage("Whether metadata contains %s in metadata", key)
                 .that(metadata.containsKey(key)).isTrue();
     }
 
@@ -176,7 +202,7 @@
         String key = RadioMetadata.METADATA_KEY_RDS_PI;
         RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
 
-        assertWithMessage("Whether metadata contains key %s not in metadata", key)
+        mExpect.withMessage("Whether metadata contains key %s not in metadata", key)
                 .that(metadata.containsKey(RadioMetadata.METADATA_KEY_ARTIST)).isFalse();
     }
 
@@ -185,7 +211,7 @@
         String key = RadioMetadata.METADATA_KEY_RDS_PI;
         RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
 
-        assertWithMessage("Int value for key %s in metadata", key)
+        mExpect.withMessage("Int value for key %s in metadata", key)
                 .that(metadata.getInt(key)).isEqualTo(INT_KEY_VALUE);
     }
 
@@ -195,7 +221,7 @@
         RadioMetadata metadata =
                 mBuilder.putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE).build();
 
-        assertWithMessage("Int value for key %s in metadata", key)
+        mExpect.withMessage("Int value for key %s in metadata", key)
                 .that(metadata.getInt(key)).isEqualTo(0);
     }
 
@@ -204,7 +230,7 @@
         String key = RadioMetadata.METADATA_KEY_ARTIST;
         RadioMetadata metadata = mBuilder.putString(key, ARTIST_KEY_VALUE).build();
 
-        assertWithMessage("String value for key %s in metadata", key)
+        mExpect.withMessage("String value for key %s in metadata", key)
                 .that(metadata.getString(key)).isEqualTo(ARTIST_KEY_VALUE);
     }
 
@@ -213,7 +239,7 @@
         String key = RadioMetadata.METADATA_KEY_ARTIST;
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("String value for key %s not in metadata", key)
+        mExpect.withMessage("String value for key %s not in metadata", key)
                 .that(metadata.getString(key)).isNull();
     }
 
@@ -222,7 +248,7 @@
         String key = RadioMetadata.METADATA_KEY_ICON;
         RadioMetadata metadata = mBuilder.putBitmap(key, mBitmapValue).build();
 
-        assertWithMessage("Bitmap value for key %s in metadata", key)
+        mExpect.withMessage("Bitmap value for key %s in metadata", key)
                 .that(metadata.getBitmap(key)).isEqualTo(mBitmapValue);
     }
 
@@ -231,16 +257,26 @@
         String key = RadioMetadata.METADATA_KEY_ICON;
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("Bitmap value for key %s not in metadata", key)
+        mExpect.withMessage("Bitmap value for key %s not in metadata", key)
                 .that(metadata.getBitmap(key)).isNull();
     }
 
     @Test
+    public void getBitmap_withIllegalKey() {
+        String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+        RadioMetadata metadata = mBuilder.putInt(RadioMetadata.METADATA_KEY_ICON, INT_KEY_VALUE)
+                .build();
+
+        mExpect.withMessage("Bitmap id value with non-bitmap-type key %s", illegalKey)
+                .that(metadata.getBitmap(illegalKey)).isNull();
+    }
+
+    @Test
     public void getBitmapId_withKeyInMetadata() {
         String key = RadioMetadata.METADATA_KEY_ART;
         RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
 
-        assertWithMessage("Bitmap id value for key %s in metadata", key)
+        mExpect.withMessage("Bitmap id value for key %s in metadata", key)
                 .that(metadata.getBitmapId(key)).isEqualTo(INT_KEY_VALUE);
     }
 
@@ -249,11 +285,21 @@
         String key = RadioMetadata.METADATA_KEY_ART;
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("Bitmap id value for key %s not in metadata", key)
+        mExpect.withMessage("Bitmap id value for key %s not in metadata", key)
                 .that(metadata.getBitmapId(key)).isEqualTo(0);
     }
 
     @Test
+    public void getBitmapId_withIllegalKey() {
+        String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+        RadioMetadata metadata = mBuilder.putInt(RadioMetadata.METADATA_KEY_ART, INT_KEY_VALUE)
+                .build();
+
+        mExpect.withMessage("Bitmap id value with non-bitmap-id-type key %s", illegalKey)
+                .that(metadata.getBitmapId(illegalKey)).isEqualTo(0);
+    }
+
+    @Test
     public void getClock_withKeyInMetadata() {
         String key = RadioMetadata.METADATA_KEY_CLOCK;
         RadioMetadata metadata = mBuilder
@@ -262,10 +308,10 @@
 
         RadioMetadata.Clock clockExpected = metadata.getClock(key);
 
-        assertWithMessage("Number of seconds since epoch of value for key %s in metadata", key)
+        mExpect.withMessage("Number of seconds since epoch of value for key %s in metadata", key)
                 .that(clockExpected.getUtcEpochSeconds())
                 .isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
-        assertWithMessage("Offset of timezone in minutes of value for key %s in metadata", key)
+        mExpect.withMessage("Offset of timezone in minutes of value for key %s in metadata", key)
                 .that(clockExpected.getTimezoneOffsetMinutes())
                 .isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
     }
@@ -275,17 +321,27 @@
         String key = RadioMetadata.METADATA_KEY_CLOCK;
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("Clock value for key %s not in metadata", key)
+        mExpect.withMessage("Clock value for key %s not in metadata", key)
                 .that(metadata.getClock(key)).isNull();
     }
 
     @Test
+    public void getClock_withIllegalKey() {
+        String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+        RadioMetadata metadata = mBuilder.putClock(RadioMetadata.METADATA_KEY_CLOCK,
+                        TEST_UTC_SECOND_SINCE_EPOCH, TEST_TIME_ZONE_OFFSET_MINUTES).build();
+
+        mExpect.withMessage("Clock value for non-clock-type key %s", illegalKey)
+                .that(metadata.getClock(illegalKey)).isNull();
+    }
+
+    @Test
     public void getStringArray_withKeyInMetadata() {
         mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         String key = RadioMetadata.METADATA_KEY_UFIDS;
         RadioMetadata metadata = mBuilder.putStringArray(key, UFIDS_VALUE).build();
 
-        assertWithMessage("String-array value for key %s not in metadata", key)
+        mExpect.withMessage("String-array value for key %s not in metadata", key)
                 .that(metadata.getStringArray(key)).asList().isEqualTo(Arrays.asList(UFIDS_VALUE));
     }
 
@@ -299,7 +355,7 @@
             metadata.getStringArray(key);
         });
 
-        assertWithMessage("Exception for getting string array for string-array value for key %s "
+        mExpect.withMessage("Exception for getting string array for string-array value for key %s "
                 + "not in metadata", key).that(thrown).hasMessageThat().contains("not found");
     }
 
@@ -312,7 +368,7 @@
             metadata.getStringArray(/* key= */ null);
         });
 
-        assertWithMessage("Exception for getting string array with null key")
+        mExpect.withMessage("Exception for getting string array with null key")
                 .that(thrown).hasMessageThat().contains("can not be null");
     }
 
@@ -326,7 +382,7 @@
             metadata.getStringArray(invalidClockKey);
         });
 
-        assertWithMessage("Exception for getting string array for key %s not of string-array type",
+        mExpect.withMessage("Exception for getting string array for non-string-array type key %s",
                 invalidClockKey).that(thrown).hasMessageThat()
                 .contains("string array");
     }
@@ -338,7 +394,7 @@
                 .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
                 .build();
 
-        assertWithMessage("Size of fields in non-empty metadata")
+        mExpect.withMessage("Size of fields in non-empty metadata")
                 .that(metadata.size()).isEqualTo(2);
     }
 
@@ -346,7 +402,7 @@
     public void size_withEmptyMetadata() {
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("Size of fields in empty metadata")
+        mExpect.withMessage("Size of fields in empty metadata")
                 .that(metadata.size()).isEqualTo(0);
     }
 
@@ -360,7 +416,7 @@
 
         Set<String> metadataSet = metadata.keySet();
 
-        assertWithMessage("Metadata set of non-empty metadata")
+        mExpect.withMessage("Metadata set of non-empty metadata")
                 .that(metadataSet).containsExactly(RadioMetadata.METADATA_KEY_ICON,
                         RadioMetadata.METADATA_KEY_RDS_PI, RadioMetadata.METADATA_KEY_ARTIST);
     }
@@ -371,7 +427,7 @@
 
         Set<String> metadataSet = metadata.keySet();
 
-        assertWithMessage("Metadata set of empty metadata")
+        mExpect.withMessage("Metadata set of empty metadata")
                 .that(metadataSet).isEmpty();
     }
 
@@ -380,7 +436,7 @@
         int nativeKey = 0;
         String key = RadioMetadata.getKeyFromNativeKey(nativeKey);
 
-        assertWithMessage("Key for native key %s", nativeKey)
+        mExpect.withMessage("Key for native key %s", nativeKey)
                 .that(key).isEqualTo(RadioMetadata.METADATA_KEY_RDS_PI);
     }
 
@@ -393,7 +449,7 @@
         RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata);
         RadioMetadata metadataCopied = copyBuilder.build();
 
-        assertWithMessage("Metadata with the same contents")
+        mExpect.withMessage("Metadata with the same contents")
                 .that(metadataCopied).isEqualTo(metadata);
     }
 
@@ -401,14 +457,15 @@
     public void describeContents_forMetadata() {
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0);
+        mExpect.withMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0);
     }
 
     @Test
     public void newArray_forRadioMetadataCreator() {
         RadioMetadata[] metadataArray = RadioMetadata.CREATOR.newArray(CREATOR_ARRAY_SIZE);
 
-        assertWithMessage("Radio metadata array").that(metadataArray).hasLength(CREATOR_ARRAY_SIZE);
+        mExpect.withMessage("Radio metadata array").that(metadataArray)
+                .hasLength(CREATOR_ARRAY_SIZE);
     }
 
     @Test
@@ -423,7 +480,7 @@
         parcel.setDataPosition(0);
 
         RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
-        assertWithMessage("Radio metadata created from parcel")
+        mExpect.withMessage("Radio metadata created from parcel")
                 .that(metadataFromParcel).isEqualTo(metadataExpected);
     }
 
@@ -441,7 +498,43 @@
         parcel.setDataPosition(0);
 
         RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
-        assertWithMessage("Radio metadata created from parcel with string array type metadata")
+        mExpect.withMessage("Radio metadata created from parcel with string array type metadata")
                 .that(metadataFromParcel).isEqualTo(metadataExpected);
     }
+
+    @Test
+    public void build_withRadioMetadataMeetingSizeRequirement() {
+        int maxBitmapSize = 10;
+        doReturn(maxBitmapSize - 1).when(mBitmapValue).getHeight();
+        doReturn(maxBitmapSize - 1).when(mBitmapValue).getWidth();
+        RadioMetadata metadataSource = mBuilder.putBitmap(
+                RadioMetadata.METADATA_KEY_ICON, mBitmapValue).build();
+
+        RadioMetadata metadata = new RadioMetadata.Builder(metadataSource, maxBitmapSize).build();
+
+        mExpect.withMessage("Bitmap without resizing")
+                .that(metadata.getBitmap(RadioMetadata.METADATA_KEY_ICON)).isEqualTo(mBitmapValue);
+    }
+
+    @Test
+    public void build_withRadioMetadataAboveSizeRequirement() {
+        int maxBitmapSize = 10;
+        int bitmapHeight = 10;
+        int bitmapWidth = 20;
+        int bitmapWidthResized = maxBitmapSize;
+        int bitmapHeightResized  = (bitmapHeight * bitmapWidthResized) / bitmapWidth;
+        Bitmap bitmapResized = mock(Bitmap.class);
+        doReturn(bitmapHeight).when(mBitmapValue).getHeight();
+        doReturn(bitmapWidth).when(mBitmapValue).getWidth();
+        doReturn(bitmapResized).when(() -> Bitmap.createScaledBitmap(
+                mBitmapValue, bitmapWidthResized, bitmapHeightResized, /* filter= */ true));
+        RadioMetadata metadataSource = mBuilder.putBitmap(
+                RadioMetadata.METADATA_KEY_ICON, mBitmapValue).build();
+
+        RadioMetadata metadata = new RadioMetadata.Builder(metadataSource, maxBitmapSize).build();
+
+        mExpect.withMessage("Bitmap with resizing")
+                .that(metadata.getBitmap(RadioMetadata.METADATA_KEY_ICON))
+                .isEqualTo(bitmapResized);
+    }
 }
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
index b36367b..e68a0b8 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
@@ -16,6 +16,8 @@
 
 package android.hardware.radio;
 
+import static org.junit.Assert.assertThrows;
+
 import android.annotation.Nullable;
 import android.os.Parcel;
 
@@ -45,6 +47,24 @@
     public final Expect expect = Expect.create();
 
     @Test
+    public void requireCriticalSecondaryIds_forDab() {
+        expect.withMessage("Critical secondary Id required for DAB")
+                .that(UniqueProgramIdentifier.requireCriticalSecondaryIds(
+                        ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT)).isTrue();
+    }
+
+    @Test
+    public void constructor_withNullSelector() {
+        ProgramSelector nullSelector = null;
+
+        NullPointerException thrown = assertThrows(NullPointerException.class,
+                () -> new UniqueProgramIdentifier(nullSelector));
+
+        expect.withMessage("Null pointer exception for unique program identifier")
+                .that(thrown).hasMessageThat().contains("can not be null");
+    }
+
+    @Test
     public void getPrimaryId_forUniqueProgramIdentifier() {
         ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
                 DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
@@ -67,6 +87,27 @@
     }
 
     @Test
+    public void getCriticalSecondaryIds_forDabUniqueProgramIdentifierWithoutEnsemble() {
+        ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
+                DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+        UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+        expect.withMessage("Critical secondary ids of DAB unique identifier without ensemble")
+                .that(dabIdentifier.getCriticalSecondaryIds())
+                .containsExactly(DAB_FREQUENCY_IDENTIFIER);
+    }
+
+    @Test
+    public void getCriticalSecondaryIds_forDabUniqueProgramIdentifierWithoutSecondaryIds() {
+        ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{},
+                /* vendorIds= */ null);
+        UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+        expect.withMessage("Critical secondary ids of DAB unique identifier")
+                .that(dabIdentifier.getCriticalSecondaryIds()).isEmpty();
+    }
+
+    @Test
     public void getCriticalSecondaryIds_forFmUniqueProgramIdentifier() {
         UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(
                 new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER,
@@ -147,6 +188,19 @@
     }
 
     @Test
+    public void equals_withMissingSecondaryIdsForUniqueProgramIdentifier() {
+        ProgramSelector dabSelector1 = getDabSelector(new ProgramSelector.Identifier[]{
+                DAB_ENSEMBLE_IDENTIFIER}, /* vendorIds= */ null);
+        UniqueProgramIdentifier dabIdentifier1 = new UniqueProgramIdentifier(dabSelector1);
+        ProgramSelector dabSelector2 = getDabSelector(new ProgramSelector.Identifier[]{
+                DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+        UniqueProgramIdentifier dabIdentifier2 = new UniqueProgramIdentifier(dabSelector2);
+
+        expect.withMessage("DAB unique identifier with missing secondary ids")
+                .that(dabIdentifier1).isNotEqualTo(dabIdentifier2);
+    }
+
+    @Test
     public void describeContents_forUniqueProgramIdentifier() {
         UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(FM_IDENTIFIER);
 
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index e18de2e..d1a90ae 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -62,6 +62,7 @@
         "frameworks-core-util-lib",
         "mockwebserver",
         "guava",
+        "android.app.usage.flags-aconfig-java",
         "android.view.accessibility.flags-aconfig-java",
         "androidx.core_core",
         "androidx.core_core-ktx",
diff --git a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
index 839b645..4565978 100644
--- a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
@@ -15,24 +15,34 @@
  */
 package android.app.usage;
 
+import static android.app.usage.Flags.FLAG_FILTER_BASED_EVENT_QUERY_API;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import android.app.usage.UsageEvents.Event;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Random;
-import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class UsageEventsQueryTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
+    @RequiresFlagsEnabled(FLAG_FILTER_BASED_EVENT_QUERY_API)
     public void testQueryDuration() {
         // Test with negative beginTimeMillis.
         long beginTimeMillis = -100;
@@ -97,6 +107,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_FILTER_BASED_EVENT_QUERY_API)
     public void testQueryEventTypes() {
         Random rnd = new Random();
         UsageEventsQuery.Builder queryBuilder = new UsageEventsQuery.Builder(1000, 2000);
@@ -104,7 +115,7 @@
         // Test with invalid event type.
         int eventType = Event.NONE - 1;
         try {
-            queryBuilder.addEventTypes(eventType);
+            queryBuilder.setEventTypes(eventType);
             fail("Invalid event type: " + eventType);
         } catch (IllegalArgumentException e) {
             // Expected, fall through.
@@ -112,7 +123,7 @@
 
         eventType = Event.MAX_EVENT_TYPE + 1;
         try {
-            queryBuilder.addEventTypes(eventType);
+            queryBuilder.setEventTypes(eventType);
             fail("Invalid event type: " + eventType);
         } catch (IllegalArgumentException e) {
             // Expected, fall through.
@@ -121,11 +132,11 @@
         // Test with valid and duplicate event types.
         eventType = rnd.nextInt(Event.MAX_EVENT_TYPE + 1);
         try {
-            UsageEventsQuery query = queryBuilder.addEventTypes(eventType, eventType, eventType)
+            UsageEventsQuery query = queryBuilder.setEventTypes(eventType, eventType, eventType)
                     .build();
-            Set<Integer> eventTypeSet = query.getEventTypes();
-            assertEquals(eventTypeSet.size(), 1);
-            int type = eventTypeSet.iterator().next();
+            final int[] eventTypesArray = query.getEventTypes();
+            assertEquals(eventTypesArray.length, 1);
+            int type = eventTypesArray[0];
             assertEquals(type, eventType);
         } catch (IllegalArgumentException e) {
             fail("Valid event type: " + eventType);
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 1617eda..e32a57b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -48,7 +48,7 @@
     @get:Rule
     val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
-    private lateinit var defaultLookupTables: SparseArray<FontScaleConverter>
+    private var defaultLookupTables: SparseArray<FontScaleConverter>? = null
 
     @Before
     fun setup() {
@@ -58,7 +58,9 @@
     @After
     fun teardown() {
         // Restore the default tables (since some tests will have added extras to the cache)
-        FontScaleConverterFactory.sLookupTables = defaultLookupTables
+        if (defaultLookupTables != null) {
+            FontScaleConverterFactory.sLookupTables = defaultLookupTables!!
+        }
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
new file mode 100644
index 0000000..27869bb
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -0,0 +1,224 @@
+/*
+ * 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 android.text
+
+import android.graphics.Paint
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val LEFT_EDGE = Paint.TEXT_RUN_FLAG_LEFT_EDGE
+const val RIGHT_EDGE = Paint.TEXT_RUN_FLAG_RIGHT_EDGE
+const val MIDDLE_OF_LINE = 0
+const val WHOLE_LINE = LEFT_EDGE or RIGHT_EDGE
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TextLineLetterSpacingTest {
+
+    @Rule
+    @JvmField
+    val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @Test
+    fun calculateRunFlagTest() {
+        // Only one Bidi run
+        assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(WHOLE_LINE)
+        assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(WHOLE_LINE)
+
+        // Two BiDi Runs.
+        // If the layout is LTR, the first run is the left most run.
+        assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(LEFT_EDGE)
+        // If the layout is LTR, the last run is the right most run.
+        assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(RIGHT_EDGE)
+        // If the layout is RTL, the first run is the right most run.
+        assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(RIGHT_EDGE)
+        // If the layout is RTL, the last run is the left most run.
+        assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(LEFT_EDGE)
+
+        // Three BiDi Runs.
+        // If the layout is LTR, the first run is the left most run.
+        assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(LEFT_EDGE)
+        // Regardless of the context direction, the middle run must not have any flags.
+        assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // If the layout is LTR, the last run is the right most run.
+        assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(RIGHT_EDGE)
+        // If the layout is RTL, the first run is the right most run.
+        assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(RIGHT_EDGE)
+        // Regardless of the context direction, the middle run must not have any flags.
+        assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // If the layout is RTL, the last run is the left most run.
+        assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(LEFT_EDGE)
+    }
+
+    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @Test
+    fun resolveRunFlagForSubSequenceTest() {
+        val runStart = 5
+        val runEnd = 15
+        // Regardless of the run directions, if the span covers entire Bidi run, the same runFlag
+        // should be returned.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(WHOLE_LINE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(WHOLE_LINE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+        // Left edge of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(LEFT_EDGE)
+        // Left edge of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(RIGHT_EDGE)
+        // Whole line of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(LEFT_EDGE)
+        // Whole line of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(RIGHT_EDGE)
+        // Middle of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+        // Left edge of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Left edge of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        // Right edge of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        // Right edge of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Whole line of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        // Whole line of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        // Middle of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+        // Left edge of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Left edge of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Whole line of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Whole line of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf3eb12..cfbda84 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,6 +19,7 @@
 import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -575,8 +576,13 @@
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_HIGH_HINT);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index 0742052..ec4c563 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -18,39 +18,67 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.util.Xml;
-
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileWriter;
 import java.io.IOException;
+import java.nio.file.Files;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MonotonicClockTest {
     private final MockClock mClock = new MockClock();
+    private File mFile;
+
+    @Before
+    public void setup() throws IOException {
+        File systemDir = Files.createTempDirectory("MonotonicClockTest").toFile();
+        mFile = new File(systemDir, "test_monotonic_clock.xml");
+        if (mFile.exists()) {
+            assertThat(mFile.delete()).isTrue();
+        }
+    }
 
     @Test
     public void persistence() throws IOException {
-        MonotonicClock monotonicClock = new MonotonicClock(1000, mClock);
+        MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock);
         mClock.realtime = 234;
 
         assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
 
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        monotonicClock.writeXml(out, Xml.newBinarySerializer());
+        monotonicClock.write();
 
         mClock.realtime = 42;
-        MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
-        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-        newMonotonicClock.readXml(in, Xml.newBinaryPullParser());
+        MonotonicClock newMonotonicClock = new MonotonicClock(mFile, 0, mClock);
 
         mClock.realtime = 2000;
         assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
     }
+
+    @Test
+    public void constructor() {
+        MonotonicClock monotonicClock = new MonotonicClock(null, 1000, mClock);
+        mClock.realtime = 234;
+
+        assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+    }
+
+    @Test
+    public void corruptedFile() throws IOException {
+        // Create an invalid binary XML file to cause IOException: "Unexpected magic number"
+        try (FileWriter w = new FileWriter(mFile)) {
+            w.write("garbage");
+        }
+
+        MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock);
+        mClock.realtime = 234;
+
+        assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+    }
 }
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 0df5b0a..dcaf676 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -19,8 +19,10 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -76,12 +78,15 @@
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
+    private ILockSettings mLockSettings;
+    private static final int USER_ID = 1;
     private static final int DEMO_USER_ID = 5;
 
     private LockPatternUtils mLockPatternUtils;
 
     private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
             throws Exception {
+        mLockSettings = Mockito.mock(ILockSettings.class);
         final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
         final MockContentResolver cr = new MockContentResolver(context);
@@ -89,15 +94,14 @@
         when(context.getContentResolver()).thenReturn(cr);
         Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
 
-        final ILockSettings ils = Mockito.mock(ILockSettings.class);
-        when(ils.getCredentialType(DEMO_USER_ID)).thenReturn(
+        when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
                 isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
                          : LockPatternUtils.CREDENTIAL_TYPE_NONE);
-        when(ils.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED, DEMO_USER_ID))
-                .thenReturn((long) PASSWORD_QUALITY_MANAGED);
+        when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
+                DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
         // TODO(b/63758238): stop spying the class under test
         mLockPatternUtils = spy(new LockPatternUtils(context));
-        when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
+        when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
         doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
 
         final UserInfo userInfo = Mockito.mock(UserInfo.class);
@@ -108,6 +112,31 @@
     }
 
     @Test
+    public void isUserInLockDown() throws Exception {
+        configureTest(true, false, 2);
+
+        // GIVEN strong auth not required
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
+
+        // THEN user isn't in lockdown
+        assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+        // GIVEN lockdown
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+        // THEN user is in lockdown
+        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+        // GIVEN lockdown and lockout
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+        // THEN user is in lockdown
+        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+    }
+
+    @Test
     public void isLockScreenDisabled_isDemoUser_true() throws Exception {
         configureTest(false, true, 2);
         assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
diff --git a/data/etc/com.android.documentsui.xml b/data/etc/com.android.documentsui.xml
index d32cbec..2e521e3 100644
--- a/data/etc/com.android.documentsui.xml
+++ b/data/etc/com.android.documentsui.xml
@@ -23,5 +23,7 @@
         <permission name="android.permission.MODIFY_QUIET_MODE"/>
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
         <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
+        <!-- Permissions required for reading device configs -->
+        <permission name="android.permission.READ_DEVICE_CONFIG" />
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 42068aa..f7c278c 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -426,6 +426,7 @@
         <!-- Permissions required for CTS test - android.server.biometrics -->
         <permission name="android.permission.USE_BIOMETRIC" />
         <permission name="android.permission.TEST_BIOMETRIC" />
+        <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
         <!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
         <permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
         <!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
@@ -511,6 +512,7 @@
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
         <!-- Permission required for CTS test - CtsTelephonyTestCases -->
         <permission name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
+        <permission name="android.permission.ACCESS_LAST_KNOWN_CELL_ID" />
         <!-- Permission required for CTS test - CtsAppTestCases -->
         <permission name="android.permission.CAPTURE_MEDIA_OUTPUT" />
         <permission name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 3dd9ba9..471acaa 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -48,12 +48,35 @@
 // Copies the font configuration file into system/etc for the product as fonts.xml.
 // Additional fonts should be installed to /product/fonts/ alongside a corresponding
 // fonts_customiztion.xml in /product/etc/
-prebuilt_etc {
-    name: "fonts.xml",
-    src: "fonts.xml",
+
+soong_config_bool_variable {
+    name: "use_var_font",
 }
 
-prebuilt_etc {
+soong_config_module_type {
+    name: "prebuilt_fonts_xml",
+    module_type: "prebuilt_etc",
+    config_namespace: "noto_sans_cjk_config",
+    bool_variables: ["use_var_font"],
+    properties: ["src"],
+}
+
+prebuilt_fonts_xml {
+    name: "fonts.xml",
+    src: "fonts.xml",
+    soong_config_variables: {
+        use_var_font: {
+            src: "fonts_cjkvf.xml",
+        },
+    },
+}
+
+prebuilt_fonts_xml {
     name: "font_fallback.xml",
     src: "font_fallback.xml",
+    soong_config_variables: {
+        use_var_font: {
+            src: "font_fallback_cjkvf.xml",
+        },
+    },
 }
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
new file mode 100644
index 0000000..70474ba
--- /dev/null
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -0,0 +1,1015 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    In this file, all fonts without names are added to the default list.
+    Fonts are chosen based on a match: full BCP-47 language tag including
+    script, then just language, and finally order (the first font containing
+    the glyph).
+
+    Order of appearance is also the tiebreaker for weight matching. This is
+    the reason why the 900 weights of Roboto precede the 700 weights - we
+    prefer the former when an 800 weight is requested. Since bold spans
+    effectively add 300 to the weight, this ensures that 900 is the bold
+    paired with the 500 weight, ensuring adequate contrast.
+
+    TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+    <!-- first font is default -->
+    <family name="sans-serif" varFamilyType="2">
+        <font>Roboto-Regular.ttf
+          <axis tag="wdth" stylevalue="100" />
+        </font>
+   </family>
+
+
+    <!-- Note that aliases must come after the fonts they reference. -->
+    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+    <alias name="sans-serif-light" to="sans-serif" weight="300" />
+    <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+    <alias name="sans-serif-black" to="sans-serif" weight="900" />
+    <alias name="arial" to="sans-serif" />
+    <alias name="helvetica" to="sans-serif" />
+    <alias name="tahoma" to="sans-serif" />
+    <alias name="verdana" to="sans-serif" />
+
+    <family name="sans-serif-condensed" varFamilyType="2">
+      <font>Roboto-Regular.ttf
+        <axis tag="wdth" stylevalue="75" />
+      </font>
+    </family>
+    <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+    <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+    <family name="serif">
+        <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+    </family>
+    <alias name="serif-bold" to="serif" weight="700" />
+    <alias name="times" to="serif" />
+    <alias name="times new roman" to="serif" />
+    <alias name="palatino" to="serif" />
+    <alias name="georgia" to="serif" />
+    <alias name="baskerville" to="serif" />
+    <alias name="goudy" to="serif" />
+    <alias name="fantasy" to="serif" />
+    <alias name="ITC Stone Serif" to="serif" />
+
+    <family name="monospace">
+        <font weight="400" style="normal">DroidSansMono.ttf</font>
+    </family>
+    <alias name="sans-serif-monospace" to="monospace" />
+    <alias name="monaco" to="monospace" />
+
+    <family name="serif-monospace">
+        <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+    </family>
+    <alias name="courier" to="serif-monospace" />
+    <alias name="courier new" to="serif-monospace" />
+
+    <family name="casual">
+        <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
+    </family>
+
+    <family name="cursive" varFamilyType="1">
+      <font>DancingScript-Regular.ttf</font>
+    </family>
+
+    <family name="sans-serif-smallcaps">
+        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+    </family>
+
+    <family name="source-sans-pro">
+        <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+        <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+        <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+        <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+        <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+        <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+    </family>
+    <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+    <family name="roboto-flex" varFamilyType="2">
+        <font>RobotoFlex-Regular.ttf
+          <axis tag="wdth" stylevalue="100" />
+        </font>
+    </family>
+
+    <!-- fallback fonts -->
+    <family lang="und-Arab" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+            NotoNaskhArabic-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+    </family>
+    <family lang="und-Arab" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+            NotoNaskhArabicUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Ethi" varFamilyType="1">
+        <font postScriptName="NotoSansEthiopic-Regular">
+            NotoSansEthiopic-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+            NotoSerifEthiopic-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Hebr">
+        <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+            NotoSansHebrew-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+    </family>
+    <family lang="und-Thai" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">
+            NotoSerifThai-Regular.ttf
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+    </family>
+    <family lang="und-Thai" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+            NotoSansThaiUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Armn" varFamilyType="1">
+        <font postScriptName="NotoSansArmenian-Regular">
+            NotoSansArmenian-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+            NotoSerifArmenian-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Geor,und-Geok" varFamilyType="1">
+        <font postScriptName="NotoSansGeorgian-Regular">
+            NotoSansGeorgian-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+            NotoSerifGeorgian-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Deva" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansDevanagari-Regular">
+            NotoSansDevanagari-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+            NotoSerifDevanagari-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Deva" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansDevanagariUI-Regular">
+            NotoSansDevanagariUI-VF.ttf
+        </font>
+    </family>
+
+    <!-- All scripts of India should come after Devanagari, due to shared
+         danda characters.
+    -->
+    <family lang="und-Gujr" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+            NotoSansGujarati-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Gujr" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+            NotoSansGujaratiUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Guru" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansGurmukhi-Regular">
+            NotoSansGurmukhi-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+            NotoSerifGurmukhi-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Guru" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansGurmukhiUI-Regular">
+            NotoSansGurmukhiUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Taml" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansTamil-Regular">
+            NotoSansTamil-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+            NotoSerifTamil-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Taml" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansTamilUI-Regular">
+            NotoSansTamilUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Mlym" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansMalayalam-Regular">
+            NotoSansMalayalam-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+            NotoSerifMalayalam-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Mlym" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansMalayalamUI-Regular">
+            NotoSansMalayalamUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Beng" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansBengali-Regular">
+            NotoSansBengali-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
+            NotoSerifBengali-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Beng" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansBengaliUI-Regular">
+            NotoSansBengaliUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Telu" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansTelugu-Regular">
+            NotoSansTelugu-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+            NotoSerifTelugu-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Telu" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansTeluguUI-Regular">
+            NotoSansTeluguUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Knda" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansKannada-Regular">
+            NotoSansKannada-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+            NotoSerifKannada-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Knda" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansKannadaUI-Regular">
+            NotoSansKannadaUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Orya" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+    </family>
+    <family lang="und-Orya" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+            NotoSansOriyaUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Sinh" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansSinhala-Regular">
+            NotoSansSinhala-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
+            NotoSerifSinhala-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Sinh" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansSinhalaUI-Regular">
+            NotoSansSinhalaUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Khmr" variant="elegant">
+        <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="26.0"/>
+        </font>
+        <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="39.0"/>
+        </font>
+        <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="58.0"/>
+        </font>
+        <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="90.0"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="108.0"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="128.0"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="151.0"/>
+        </font>
+        <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="169.0"/>
+        </font>
+        <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="190.0"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+    </family>
+    <family lang="und-Khmr" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+            NotoSansKhmerUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Laoo" variant="elegant">
+        <font weight="400" style="normal">NotoSansLao-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">
+            NotoSerifLao-Regular.ttf
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+    </family>
+    <family lang="und-Laoo" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Mymr" variant="elegant">
+        <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+    </family>
+    <family lang="und-Mymr" variant="compact">
+        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+    </family>
+    <family lang="und-Thaa">
+        <font weight="400" style="normal" postScriptName="NotoSansThaana">
+            NotoSansThaana-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+    </family>
+    <family lang="und-Cham">
+        <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+    </family>
+    <family lang="und-Ahom">
+        <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+    </family>
+    <family lang="und-Adlm" varFamilyType="1">
+        <font postScriptName="NotoSansAdlam-Regular">
+            NotoSansAdlam-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Avst">
+        <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+            NotoSansAvestan-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bali">
+        <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+            NotoSansBalinese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bamu">
+        <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Batk">
+        <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Brah">
+        <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+            NotoSansBrahmi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bugi">
+        <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+            NotoSansBuginese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Buhd">
+        <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cans">
+        <font weight="400" style="normal">
+            NotoSansCanadianAboriginal-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cari">
+        <font weight="400" style="normal" postScriptName="NotoSansCarian">
+            NotoSansCarian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cakm">
+        <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+    </family>
+    <family lang="und-Cher">
+        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+    </family>
+    <family lang="und-Copt">
+        <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+            NotoSansCoptic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Xsux">
+        <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+            NotoSansCuneiform-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cprt">
+        <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+            NotoSansCypriot-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Dsrt">
+        <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+            NotoSansDeseret-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Egyp">
+        <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+            NotoSansEgyptianHieroglyphs-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Elba">
+        <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+    </family>
+    <family lang="und-Glag">
+        <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+            NotoSansGlagolitic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Goth">
+        <font weight="400" style="normal" postScriptName="NotoSansGothic">
+            NotoSansGothic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Hano">
+        <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+            NotoSansHanunoo-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Armi">
+        <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+            NotoSansImperialAramaic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phli">
+        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+            NotoSansInscriptionalPahlavi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Prti">
+        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+            NotoSansInscriptionalParthian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Java">
+        <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+    </family>
+    <family lang="und-Kthi">
+        <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+            NotoSansKaithi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Kali">
+        <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+            NotoSansKayahLi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Khar">
+        <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+            NotoSansKharoshthi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lepc">
+        <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+            NotoSansLepcha-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Limb">
+        <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Linb">
+        <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+            NotoSansLinearB-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lisu">
+        <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lyci">
+        <font weight="400" style="normal" postScriptName="NotoSansLycian">
+            NotoSansLycian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lydi">
+        <font weight="400" style="normal" postScriptName="NotoSansLydian">
+            NotoSansLydian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Mand">
+        <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+            NotoSansMandaic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Mtei">
+        <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+            NotoSansMeeteiMayek-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Talu">
+        <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+            NotoSansNewTaiLue-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Nkoo">
+        <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Ogam">
+        <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Olck">
+        <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+            NotoSansOlChiki-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Ital">
+        <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+            NotoSansOldItalic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Xpeo">
+        <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+            NotoSansOldPersian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sarb">
+        <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+            NotoSansOldSouthArabian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Orkh">
+        <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+            NotoSansOldTurkic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Osge">
+        <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+    </family>
+    <family lang="und-Osma">
+        <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+            NotoSansOsmanya-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phnx">
+        <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+            NotoSansPhoenician-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Rjng">
+        <font weight="400" style="normal" postScriptName="NotoSansRejang">
+            NotoSansRejang-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Runr">
+        <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Samr">
+        <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+            NotoSansSamaritan-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Saur">
+        <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+            NotoSansSaurashtra-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Shaw">
+        <font weight="400" style="normal" postScriptName="NotoSansShavian">
+            NotoSansShavian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sund">
+        <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+            NotoSansSundanese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sylo">
+        <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+            NotoSansSylotiNagri-Regular.ttf
+        </font>
+    </family>
+    <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+    <family lang="und-Syre">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+            NotoSansSyriacEstrangela-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Syrn">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+            NotoSansSyriacEastern-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Syrj">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+            NotoSansSyriacWestern-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tglg">
+        <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+            NotoSansTagalog-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tagb">
+        <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+            NotoSansTagbanwa-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lana">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+            NotoSansTaiTham-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tavt">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+            NotoSansTaiViet-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tibt" varFamilyType="1">
+        <font postScriptName="NotoSerifTibetan-Regular">
+            NotoSerifTibetan-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Tfng">
+        <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+    </family>
+    <family lang="und-Ugar">
+        <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+            NotoSansUgaritic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Vaii">
+        <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+        </font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+    </family>
+    <family lang="zh-Hans">
+        <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="2" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="zh-Hant,zh-Bopo">
+        <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="3" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="ja">
+        <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="0" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="ko">
+        <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="1" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="und-Zsye">
+        <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+    </family>
+    <family lang="und-Zsye">
+        <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+    </family>
+    <family lang="und-Zsym">
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+    </family>
+    <!--
+        Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+        override the East Asian punctuation for Chinese.
+    -->
+    <family lang="und-Tale">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Yiii">
+        <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+    </family>
+    <family lang="und-Mong">
+        <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+            NotoSansMongolian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phag">
+        <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+            NotoSansPhagsPa-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Hluw">
+        <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+    </family>
+    <family lang="und-Bass">
+        <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+    </family>
+    <family lang="und-Bhks">
+        <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+    </family>
+    <family lang="und-Hatr">
+        <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+    </family>
+    <family lang="und-Lina">
+        <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+    </family>
+    <family lang="und-Mani">
+        <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+    </family>
+    <family lang="und-Marc">
+        <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+    </family>
+    <family lang="und-Merc">
+        <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+    </family>
+    <family lang="und-Plrd">
+        <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+    </family>
+    <family lang="und-Mroo">
+        <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+    </family>
+    <family lang="und-Mult">
+        <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+    </family>
+    <family lang="und-Nbat">
+        <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+    </family>
+    <family lang="und-Newa">
+        <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+    </family>
+    <family lang="und-Narb">
+        <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+    </family>
+    <family lang="und-Perm">
+        <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+    </family>
+    <family lang="und-Hmng">
+        <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+    </family>
+    <family lang="und-Palm">
+        <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+    </family>
+    <family lang="und-Pauc">
+        <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+    </family>
+    <family lang="und-Shrd">
+        <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+    </family>
+    <family lang="und-Sora">
+        <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+    </family>
+    <family lang="und-Gong">
+        <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+    </family>
+    <family lang="und-Rohg">
+        <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+    </family>
+    <family lang="und-Khoj">
+        <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+    </family>
+    <family lang="und-Gonm">
+        <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+    </family>
+    <family lang="und-Wcho">
+        <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+    </family>
+    <family lang="und-Wara">
+        <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+    </family>
+    <family lang="und-Gran">
+        <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+    </family>
+    <family lang="und-Modi">
+        <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+    </family>
+    <family lang="und-Dogr">
+        <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+    </family>
+    <family lang="und-Medf" varFamilyType="1">
+        <font postScriptName="NotoSansMedefaidrin-Regular">
+            NotoSansMedefaidrin-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Soyo" varFamilyType="1">
+        <font postScriptName="NotoSansSoyombo-Regular">
+            NotoSansSoyombo-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Takr" varFamilyType="1">
+        <font postScriptName="NotoSansTakri-Regular">
+            NotoSansTakri-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Hmnp" varFamilyType="1">
+        <font postScriptName="NotoSerifHmongNyiakeng-Regular">
+            NotoSerifNyiakengPuachueHmong-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Yezi" varFamilyType="1">
+        <font postScriptName="NotoSerifYezidi-Regular">
+            NotoSerifYezidi-VF.ttf
+        </font>
+    </family>
+</familyset>
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
new file mode 100644
index 0000000..8d91edd
--- /dev/null
+++ b/data/fonts/fonts_cjkvf.xml
@@ -0,0 +1,1785 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    DEPRECATED: This XML file is no longer a source of the font files installed
+    in the system.
+
+    For the device vendors: please add your font configurations to the
+    platform/frameworks/base/data/font_fallback.xml and also add it to this XML
+    file as much as possible for apps that reads this XML file.
+
+    For the application developers: please stop reading this XML file and use
+    android.graphics.fonts.SystemFonts#getAvailableFonts Java API or
+    ASystemFontIterator_open NDK API for getting list of system installed
+    font files.
+
+    WARNING: Parsing of this file by third-party apps is not supported. The
+    file, and the font files it refers to, will be renamed and/or moved out
+    from their respective location in the next Android release, and/or the
+    format or syntax of the file may change significantly. If you parse this
+    file for information about system fonts, do it at your own risk. Your
+    application will almost certainly break with the next major Android
+    release.
+
+    In this file, all fonts without names are added to the default list.
+    Fonts are chosen based on a match: full BCP-47 language tag including
+    script, then just language, and finally order (the first font containing
+    the glyph).
+
+    Order of appearance is also the tiebreaker for weight matching. This is
+    the reason why the 900 weights of Roboto precede the 700 weights - we
+    prefer the former when an 800 weight is requested. Since bold spans
+    effectively add 300 to the weight, this ensures that 900 is the bold
+    paired with the 500 weight, ensuring adequate contrast.
+
+    TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+    <!-- first font is default -->
+    <family name="sans-serif">
+        <font weight="100" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+        <font weight="100" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+   </family>
+
+
+    <!-- Note that aliases must come after the fonts they reference. -->
+    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+    <alias name="sans-serif-light" to="sans-serif" weight="300" />
+    <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+    <alias name="sans-serif-black" to="sans-serif" weight="900" />
+    <alias name="arial" to="sans-serif" />
+    <alias name="helvetica" to="sans-serif" />
+    <alias name="tahoma" to="sans-serif" />
+    <alias name="verdana" to="sans-serif" />
+
+    <family name="sans-serif-condensed">
+      <font weight="100" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="100" />
+      </font>
+      <font weight="200" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="200" />
+      </font>
+      <font weight="300" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="300" />
+      </font>
+      <font weight="400" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="400" />
+      </font>
+      <font weight="500" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="500" />
+      </font>
+      <font weight="600" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="600" />
+      </font>
+      <font weight="700" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="700" />
+      </font>
+      <font weight="800" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="800" />
+      </font>
+      <font weight="900" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="900" />
+      </font>
+      <font weight="100" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="100" />
+      </font>
+      <font weight="200" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="200" />
+      </font>
+      <font weight="300" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="300" />
+      </font>
+      <font weight="400" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="400" />
+      </font>
+      <font weight="500" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="500" />
+      </font>
+      <font weight="600" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="600" />
+      </font>
+      <font weight="700" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="700" />
+      </font>
+      <font weight="800" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="800" />
+      </font>
+      <font weight="900" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="900" />
+      </font>
+    </family>
+    <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+    <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+    <family name="serif">
+        <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+    </family>
+    <alias name="serif-bold" to="serif" weight="700" />
+    <alias name="times" to="serif" />
+    <alias name="times new roman" to="serif" />
+    <alias name="palatino" to="serif" />
+    <alias name="georgia" to="serif" />
+    <alias name="baskerville" to="serif" />
+    <alias name="goudy" to="serif" />
+    <alias name="fantasy" to="serif" />
+    <alias name="ITC Stone Serif" to="serif" />
+
+    <family name="monospace">
+        <font weight="400" style="normal">DroidSansMono.ttf</font>
+    </family>
+    <alias name="sans-serif-monospace" to="monospace" />
+    <alias name="monaco" to="monospace" />
+
+    <family name="serif-monospace">
+        <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+    </family>
+    <alias name="courier" to="serif-monospace" />
+    <alias name="courier new" to="serif-monospace" />
+
+    <family name="casual">
+        <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
+    </family>
+
+    <family name="cursive">
+      <font weight="400" style="normal">DancingScript-Regular.ttf
+        <axis tag="wght" stylevalue="400" />
+      </font>
+      <font weight="700" style="normal">DancingScript-Regular.ttf
+        <axis tag="wght" stylevalue="700" />
+      </font>
+    </family>
+
+    <family name="sans-serif-smallcaps">
+        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+    </family>
+
+    <family name="source-sans-pro">
+        <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+        <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+        <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+        <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+        <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+        <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+    </family>
+    <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+    <family name="roboto-flex">
+        <font weight="100" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+        <font weight="100" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+    </family>
+
+    <!-- fallback fonts -->
+    <family lang="und-Arab" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+            NotoNaskhArabic-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+    </family>
+    <family lang="und-Arab" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+            NotoNaskhArabicUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Ethi">
+        <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular">
+            NotoSansEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular">
+            NotoSansEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansEthiopic-Regular">
+            NotoSansEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansEthiopic-Regular">
+            NotoSansEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Hebr">
+        <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+            NotoSansHebrew-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+    </family>
+    <family lang="und-Thai" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">
+            NotoSerifThai-Regular.ttf
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+    </family>
+    <family lang="und-Thai" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+            NotoSansThaiUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Armn">
+        <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular">
+            NotoSansArmenian-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular">
+            NotoSansArmenian-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular">
+            NotoSansArmenian-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular">
+            NotoSansArmenian-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Geor,und-Geok">
+        <font weight="400" style="normal" postScriptName="NotoSansGeorgian-Regular">
+            NotoSansGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansGeorgian-Regular">
+            NotoSansGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansGeorgian-Regular">
+            NotoSansGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansGeorgian-Regular">
+            NotoSansGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Deva" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular">
+            NotoSansDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular">
+            NotoSansDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular">
+            NotoSansDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansDevanagari-Regular">
+            NotoSansDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Deva" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+            NotoSansDevanagariUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+            NotoSansDevanagariUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+            NotoSansDevanagariUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+            NotoSansDevanagariUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+
+    <!-- All scripts of India should come after Devanagari, due to shared
+         danda characters.
+    -->
+    <family lang="und-Gujr" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+            NotoSansGujarati-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Gujr" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+            NotoSansGujaratiUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Guru" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+            NotoSansGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+            NotoSansGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+            NotoSansGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+            NotoSansGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Guru" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+            NotoSansGurmukhiUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+            NotoSansGurmukhiUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+            NotoSansGurmukhiUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+            NotoSansGurmukhiUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Taml" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular">
+            NotoSansTamil-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular">
+            NotoSansTamil-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansTamil-Regular">
+            NotoSansTamil-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansTamil-Regular">
+            NotoSansTamil-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Taml" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular">
+            NotoSansTamilUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular">
+            NotoSansTamilUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular">
+            NotoSansTamilUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular">
+            NotoSansTamilUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Mlym" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular">
+            NotoSansMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular">
+            NotoSansMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansMalayalam-Regular">
+            NotoSansMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansMalayalam-Regular">
+            NotoSansMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Mlym" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+            NotoSansMalayalamUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+            NotoSansMalayalamUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+            NotoSansMalayalamUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+            NotoSansMalayalamUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Beng" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular">
+            NotoSansBengali-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular">
+            NotoSansBengali-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansBengali-Regular">
+            NotoSansBengali-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansBengali-Regular">
+            NotoSansBengali-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Beng" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+            NotoSansBengaliUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+            NotoSansBengaliUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+            NotoSansBengaliUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+            NotoSansBengaliUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Telu" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular">
+            NotoSansTelugu-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular">
+            NotoSansTelugu-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansTelugu-Regular">
+            NotoSansTelugu-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansTelugu-Regular">
+            NotoSansTelugu-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Telu" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+            NotoSansTeluguUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+            NotoSansTeluguUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+            NotoSansTeluguUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+            NotoSansTeluguUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Knda" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansKannada-Regular">
+            NotoSansKannada-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansKannada-Regular">
+            NotoSansKannada-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansKannada-Regular">
+            NotoSansKannada-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansKannada-Regular">
+            NotoSansKannada-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Knda" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+            NotoSansKannadaUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+            NotoSansKannadaUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+            NotoSansKannadaUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+            NotoSansKannadaUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Orya" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+    </family>
+    <family lang="und-Orya" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+            NotoSansOriyaUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Sinh" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansSinhala-Regular">
+            NotoSansSinhala-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansSinhala-Regular">
+            NotoSansSinhala-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansSinhala-Regular">
+            NotoSansSinhala-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansSinhala-Regular">
+            NotoSansSinhala-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Sinh" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+            NotoSansSinhalaUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+            NotoSansSinhalaUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+            NotoSansSinhalaUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+            NotoSansSinhalaUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Khmr" variant="elegant">
+        <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="26.0"/>
+        </font>
+        <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="39.0"/>
+        </font>
+        <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="58.0"/>
+        </font>
+        <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="90.0"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="108.0"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="128.0"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="151.0"/>
+        </font>
+        <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="169.0"/>
+        </font>
+        <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="190.0"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+    </family>
+    <family lang="und-Khmr" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+            NotoSansKhmerUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Laoo" variant="elegant">
+        <font weight="400" style="normal">NotoSansLao-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">
+            NotoSerifLao-Regular.ttf
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+    </family>
+    <family lang="und-Laoo" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Mymr" variant="elegant">
+        <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+    </family>
+    <family lang="und-Mymr" variant="compact">
+        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+    </family>
+    <family lang="und-Thaa">
+        <font weight="400" style="normal" postScriptName="NotoSansThaana">
+            NotoSansThaana-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+    </family>
+    <family lang="und-Cham">
+        <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+    </family>
+    <family lang="und-Ahom">
+        <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+    </family>
+    <family lang="und-Adlm">
+        <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular">
+            NotoSansAdlam-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular">
+            NotoSansAdlam-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansAdlam-Regular">
+            NotoSansAdlam-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansAdlam-Regular">
+            NotoSansAdlam-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Avst">
+        <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+            NotoSansAvestan-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bali">
+        <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+            NotoSansBalinese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bamu">
+        <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Batk">
+        <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Brah">
+        <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+            NotoSansBrahmi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bugi">
+        <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+            NotoSansBuginese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Buhd">
+        <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cans">
+        <font weight="400" style="normal">
+            NotoSansCanadianAboriginal-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cari">
+        <font weight="400" style="normal" postScriptName="NotoSansCarian">
+            NotoSansCarian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cakm">
+        <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+    </family>
+    <family lang="und-Cher">
+        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+    </family>
+    <family lang="und-Copt">
+        <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+            NotoSansCoptic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Xsux">
+        <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+            NotoSansCuneiform-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cprt">
+        <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+            NotoSansCypriot-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Dsrt">
+        <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+            NotoSansDeseret-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Egyp">
+        <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+            NotoSansEgyptianHieroglyphs-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Elba">
+        <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+    </family>
+    <family lang="und-Glag">
+        <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+            NotoSansGlagolitic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Goth">
+        <font weight="400" style="normal" postScriptName="NotoSansGothic">
+            NotoSansGothic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Hano">
+        <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+            NotoSansHanunoo-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Armi">
+        <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+            NotoSansImperialAramaic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phli">
+        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+            NotoSansInscriptionalPahlavi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Prti">
+        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+            NotoSansInscriptionalParthian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Java">
+        <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+    </family>
+    <family lang="und-Kthi">
+        <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+            NotoSansKaithi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Kali">
+        <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+            NotoSansKayahLi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Khar">
+        <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+            NotoSansKharoshthi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lepc">
+        <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+            NotoSansLepcha-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Limb">
+        <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Linb">
+        <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+            NotoSansLinearB-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lisu">
+        <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lyci">
+        <font weight="400" style="normal" postScriptName="NotoSansLycian">
+            NotoSansLycian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lydi">
+        <font weight="400" style="normal" postScriptName="NotoSansLydian">
+            NotoSansLydian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Mand">
+        <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+            NotoSansMandaic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Mtei">
+        <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+            NotoSansMeeteiMayek-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Talu">
+        <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+            NotoSansNewTaiLue-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Nkoo">
+        <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Ogam">
+        <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Olck">
+        <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+            NotoSansOlChiki-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Ital">
+        <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+            NotoSansOldItalic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Xpeo">
+        <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+            NotoSansOldPersian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sarb">
+        <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+            NotoSansOldSouthArabian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Orkh">
+        <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+            NotoSansOldTurkic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Osge">
+        <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+    </family>
+    <family lang="und-Osma">
+        <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+            NotoSansOsmanya-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phnx">
+        <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+            NotoSansPhoenician-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Rjng">
+        <font weight="400" style="normal" postScriptName="NotoSansRejang">
+            NotoSansRejang-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Runr">
+        <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Samr">
+        <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+            NotoSansSamaritan-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Saur">
+        <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+            NotoSansSaurashtra-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Shaw">
+        <font weight="400" style="normal" postScriptName="NotoSansShavian">
+            NotoSansShavian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sund">
+        <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+            NotoSansSundanese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sylo">
+        <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+            NotoSansSylotiNagri-Regular.ttf
+        </font>
+    </family>
+    <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+    <family lang="und-Syre">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+            NotoSansSyriacEstrangela-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Syrn">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+            NotoSansSyriacEastern-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Syrj">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+            NotoSansSyriacWestern-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tglg">
+        <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+            NotoSansTagalog-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tagb">
+        <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+            NotoSansTagbanwa-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lana">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+            NotoSansTaiTham-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tavt">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+            NotoSansTaiViet-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tibt">
+        <font weight="400" style="normal" postScriptName="NotoSerifTibetan-Regular">
+            NotoSerifTibetan-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSerifTibetan-Regular">
+            NotoSerifTibetan-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSerifTibetan-Regular">
+            NotoSerifTibetan-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSerifTibetan-Regular">
+            NotoSerifTibetan-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Tfng">
+        <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+    </family>
+    <family lang="und-Ugar">
+        <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+            NotoSansUgaritic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Vaii">
+        <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+        </font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+    </family>
+    <family lang="zh-Hans">
+        <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="2" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="zh-Hant,zh-Bopo">
+        <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="3" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="ja">
+        <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="0" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="ko">
+        <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="1" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="und-Zsye" ignore="true">
+        <font weight="400" style="normal">NotoColorEmojiLegacy.ttf</font>
+    </family>
+    <family lang="und-Zsye">
+        <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+    </family>
+    <family lang="und-Zsye">
+        <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+    </family>
+    <family lang="und-Zsym">
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+    </family>
+    <!--
+        Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+        override the East Asian punctuation for Chinese.
+    -->
+    <family lang="und-Tale">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Yiii">
+        <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+    </family>
+    <family lang="und-Mong">
+        <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+            NotoSansMongolian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phag">
+        <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+            NotoSansPhagsPa-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Hluw">
+        <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+    </family>
+    <family lang="und-Bass">
+        <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+    </family>
+    <family lang="und-Bhks">
+        <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+    </family>
+    <family lang="und-Hatr">
+        <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+    </family>
+    <family lang="und-Lina">
+        <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+    </family>
+    <family lang="und-Mani">
+        <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+    </family>
+    <family lang="und-Marc">
+        <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+    </family>
+    <family lang="und-Merc">
+        <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+    </family>
+    <family lang="und-Plrd">
+        <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+    </family>
+    <family lang="und-Mroo">
+        <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+    </family>
+    <family lang="und-Mult">
+        <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+    </family>
+    <family lang="und-Nbat">
+        <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+    </family>
+    <family lang="und-Newa">
+        <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+    </family>
+    <family lang="und-Narb">
+        <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+    </family>
+    <family lang="und-Perm">
+        <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+    </family>
+    <family lang="und-Hmng">
+        <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+    </family>
+    <family lang="und-Palm">
+        <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+    </family>
+    <family lang="und-Pauc">
+        <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+    </family>
+    <family lang="und-Shrd">
+        <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+    </family>
+    <family lang="und-Sora">
+        <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+    </family>
+    <family lang="und-Gong">
+        <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+    </family>
+    <family lang="und-Rohg">
+        <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+    </family>
+    <family lang="und-Khoj">
+        <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+    </family>
+    <family lang="und-Gonm">
+        <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+    </family>
+    <family lang="und-Wcho">
+        <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+    </family>
+    <family lang="und-Wara">
+        <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+    </family>
+    <family lang="und-Gran">
+        <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+    </family>
+    <family lang="und-Modi">
+        <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+    </family>
+    <family lang="und-Dogr">
+        <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+    </family>
+    <family lang="und-Medf">
+        <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+            NotoSansMedefaidrin-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+            NotoSansMedefaidrin-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+            NotoSansMedefaidrin-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+            NotoSansMedefaidrin-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Soyo">
+        <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular">
+            NotoSansSoyombo-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular">
+            NotoSansSoyombo-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansSoyombo-Regular">
+            NotoSansSoyombo-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansSoyombo-Regular">
+            NotoSansSoyombo-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Takr">
+        <font weight="400" style="normal" postScriptName="NotoSansTakri-Regular">
+            NotoSansTakri-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular">
+            NotoSansTakri-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular">
+            NotoSansTakri-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular">
+            NotoSansTakri-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Hmnp">
+        <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+            NotoSerifNyiakengPuachueHmong-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+            NotoSerifNyiakengPuachueHmong-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+            NotoSerifNyiakengPuachueHmong-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+            NotoSerifNyiakengPuachueHmong-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Yezi">
+        <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular">
+            NotoSerifYezidi-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular">
+            NotoSerifYezidi-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSerifYezidi-Regular">
+            NotoSerifYezidi-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSerifYezidi-Regular">
+            NotoSerifYezidi-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+</familyset>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index f6ba103..ae61a2d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,6 +17,7 @@
 package android.graphics;
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
+import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
 
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
@@ -133,7 +134,9 @@
             FAKE_BOLD_TEXT_FLAG,
             LINEAR_TEXT_FLAG,
             SUBPIXEL_TEXT_FLAG,
-            EMBEDDED_BITMAP_TEXT_FLAG
+            EMBEDDED_BITMAP_TEXT_FLAG,
+            TEXT_RUN_FLAG_LEFT_EDGE,
+            TEXT_RUN_FLAG_RIGHT_EDGE
     })
     public @interface PaintFlag{}
 
@@ -264,6 +267,66 @@
     /** @hide bit mask for the flag enabling vertical rendering for text */
     public static final int VERTICAL_TEXT_FLAG = 0x1000;
 
+    /**
+     * A text run flag that indicates the run is located the visually most left segment of the line.
+     * <p>
+     * This flag is used for telling the underlying text layout engine that the text is located at
+     * the most left of the line. This flag is used for controlling the amount letter spacing
+     * added. If the text is in the middle of the line, the text layout engine assigns additional
+     * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+     * not be added to the visually most left and right of the line. By setting this flag, text
+     * layout engine calculates the layout as it is located at the most visually left of the line
+     * and doesn't add letter spacing to the left of this run.
+     * <p>
+     * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+     * if the target run is located visually most left position. This left does not always mean the
+     * beginning of the text.
+     * <p>
+     * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_RIGHT_EDGE} as well.
+     * <p>
+     * Note that this flag is only effective for run based APIs. For example, this flag works for
+     * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+     * and
+     * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+     * However, this doesn't work for
+     * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+     * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+     * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+     */
+    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
+
+
+    /**
+     * A text run flag that indicates the run is located the visually most right segment of the
+     * line.
+     * <p>
+     * This flag is used for telling the underlying text layout engine that the text is located at
+     * the most right of the line. This flag is used for controlling the amount letter spacing
+     * added. If the text is in the middle of the line, the text layout engine assigns additional
+     * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+     * not be added to the visually most left and right of the line. By setting this flag, text
+     * layout engine calculates the layout as it is located at the most visually left of the line
+     * and doesn't add letter spacing to the left of this run.
+     * <p>
+     * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+     * if the target run is located visually most right position. This right does not always mean
+     * the end of the text.
+     * <p>
+     * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_LEFT_EDGE} as well.
+     * <p>
+     * Note that this flag is only effective for run based APIs. For example, this flag works for
+     * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+     * and
+     * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+     * However, this doesn't work for
+     * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+     * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+     * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+     */
+    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
+
     // These flags are always set on a new/reset paint, even if flags 0 is passed.
     static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG
             | FILTER_BITMAP_FLAG;
@@ -2520,17 +2583,24 @@
         if (text.length == 0 || count == 0) {
             return 0f;
         }
-        if (!mHasCompatScaling) {
-            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
-                    index, count, index, count, mBidiFlags, null, 0));
-        }
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
 
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
-                mBidiFlags, null, 0);
-        setTextSize(oldSize);
-        return (float) Math.ceil(w*mInvCompatScaling);
+            if (!mHasCompatScaling) {
+                return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+                        index, count, index, count, mBidiFlags, null, 0));
+            }
+
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
+                    mBidiFlags, null, 0);
+            setTextSize(oldSize);
+            return (float) Math.ceil(w * mInvCompatScaling);
+        } finally {
+            setFlags(oldFlag);
+        }
     }
 
     /**
@@ -2552,16 +2622,22 @@
         if (text.length() == 0 || start == end) {
             return 0f;
         }
-        if (!mHasCompatScaling) {
-            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
-                    start, end, start, end, mBidiFlags, null, 0));
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
+            if (!mHasCompatScaling) {
+                return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+                        start, end, start, end, mBidiFlags, null, 0));
+            }
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
+                    null, 0);
+            setTextSize(oldSize);
+            return (float) Math.ceil(w * mInvCompatScaling);
+        } finally {
+            setFlags(oldFlag);
         }
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
-                null, 0);
-        setTextSize(oldSize);
-        return (float) Math.ceil(w * mInvCompatScaling);
     }
 
     /**
@@ -2766,19 +2842,26 @@
         if (text.length == 0 || count == 0) {
             return 0;
         }
-        if (!mHasCompatScaling) {
-            nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
-            return count;
-        }
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
+            if (!mHasCompatScaling) {
+                nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths,
+                        0);
+                return count;
+            }
 
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
-        setTextSize(oldSize);
-        for (int i = 0; i < count; i++) {
-            widths[i] *= mInvCompatScaling;
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
+            setTextSize(oldSize);
+            for (int i = 0; i < count; i++) {
+                widths[i] *= mInvCompatScaling;
+            }
+            return count;
+        } finally {
+            setFlags(oldFlag);
         }
-        return count;
     }
 
     /**
@@ -2849,19 +2932,25 @@
         if (text.length() == 0 || start == end) {
             return 0;
         }
-        if (!mHasCompatScaling) {
-            nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
-            return end - start;
-        }
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
+            if (!mHasCompatScaling) {
+                nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+                return end - start;
+            }
 
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
-        setTextSize(oldSize);
-        for (int i = 0; i < end - start; i++) {
-            widths[i] *= mInvCompatScaling;
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+            setTextSize(oldSize);
+            for (int i = 0; i < end - start; i++) {
+                widths[i] *= mInvCompatScaling;
+            }
+            return end - start;
+        } finally {
+            setFlags(oldFlag);
         }
-        return end - start;
     }
 
     /**
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 2d33e8d..6da0719 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -269,6 +269,10 @@
          * offset is zero. After the style is applied the internal offset is moved to {@code offset
          * + length}, and next call will start from this new position.
          *
+         * <p>
+         * {@link Paint#TEXT_RUN_FLAG_RIGHT_EDGE} and {@link Paint#TEXT_RUN_FLAG_LEFT_EDGE} are
+         * ignored and treated as both of them are set.
+         *
          * @param paint a paint
          * @param length a length to be applied with a given paint, can not exceed the length of the
          *               text
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 4ec5e1b..6404c4b 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -100,12 +100,14 @@
      *
      * @param userId - the user's Android user ID
      * @param unlockingSids - list of biometric SIDs with which the device may be unlocked again
+     * @param weakUnlockEnabled - true if non-strong biometric or trust agent unlock is enabled
      * @return 0 if successful or a {@code ResponseCode}.
      */
-    public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids) {
+    public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
+            boolean weakUnlockEnabled) {
         StrictMode.noteDiskWrite();
         try {
-            getService().onDeviceLocked(userId, unlockingSids);
+            getService().onDeviceLocked(userId, unlockingSids, weakUnlockEnabled);
             return 0;
         } catch (RemoteException | NullPointerException e) {
             Log.w(TAG, "Can not connect to keystore", e);
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 45540e0..4cdc06a 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -162,6 +162,7 @@
         "com_android_wm_shell_flags_lib",
         "com.android.window.flags.window-aconfig-java",
         "WindowManager-Shell-proto",
+        "perfetto_trace_java_protos",
         "dagger2",
         "jsr330",
     ],
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 901d5fa..0e04658 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -1,13 +1,6 @@
 package: "com.android.wm.shell"
 
 flag {
-    name: "example_flag"
-    namespace: "multitasking"
-    description: "An Example Flag"
-    bug: "300136750"
-}
-
-flag {
     name: "enable_app_pairs"
     namespace: "multitasking"
     description: "Enables the ability to create and save app pairs to the Home screen"
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index e4abae4..9854e58 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -134,6 +134,13 @@
     <!-- Whether the additional education about reachability is enabled -->
     <bool name="config_letterboxIsReachabilityEducationEnabled">false</bool>
 
+    <!-- The minimum tolerance of the percentage of activity bounds within its task to hide
+         size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+         100 is the default value where the activity has to fit exactly within the task to allow
+         size compat restart button to be hidden. 0 means size compat restart button will always
+         be hidden. -->
+    <integer name="config_letterboxRestartButtonHideTolerance">100</integer>
+
     <!-- Whether DragAndDrop capability is enabled -->
     <bool name="config_enableShellDragDrop">true</bool>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 09d99b2..fa2e236 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -71,6 +71,8 @@
     private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
             "has_seen_vertical_reachability_education";
 
+    private static final int MAX_PERCENTAGE_VAL = 100;
+
     /**
      * The {@link SharedPreferences} instance for the restart dialog and the reachability
      * education.
@@ -82,6 +84,12 @@
      */
     private final SharedPreferences mLetterboxEduSharedPreferences;
 
+    /**
+     * The minimum tolerance of the percentage of activity bounds within its task to hide
+     * size compat restart button.
+     */
+    private final int mHideSizeCompatRestartButtonTolerance;
+
     // Whether the extended restart dialog is enabled
     private boolean mIsRestartDialogEnabled;
 
@@ -106,6 +114,9 @@
                 R.bool.config_letterboxIsRestartDialogEnabled);
         mIsReachabilityEducationEnabled = context.getResources().getBoolean(
                 R.bool.config_letterboxIsReachabilityEducationEnabled);
+        final int tolerance = context.getResources().getInteger(
+                R.integer.config_letterboxRestartButtonHideTolerance);
+        mHideSizeCompatRestartButtonTolerance = getHideSizeCompatRestartButtonTolerance(tolerance);
         mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
                 DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
@@ -179,6 +190,10 @@
                     || !hasSeenVerticalReachabilityEducation(taskInfo));
     }
 
+    int getHideSizeCompatRestartButtonTolerance() {
+        return mHideSizeCompatRestartButtonTolerance;
+    }
+
     boolean getHasSeenLetterboxEducation(int userId) {
         return mLetterboxEduSharedPreferences
                 .getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
@@ -218,6 +233,15 @@
         }
     }
 
+    // Returns the minimum tolerance of the percentage of activity bounds within its task to hide
+    // size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+    // 100 is the default value where the activity has to fit exactly within the task to allow
+    // size compat restart button to be hidden. 0 means size compat restart button will always
+    // be hidden.
+    private int getHideSizeCompatRestartButtonTolerance(int tolerance) {
+        return tolerance < 0 || tolerance > MAX_PERCENTAGE_VAL ? MAX_PERCENTAGE_VAL : tolerance;
+    }
+
     private boolean isReachabilityEducationEnabled() {
         return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
                 && mIsLetterboxReachabilityEducationAllowed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 00e0cdb..2dd2743 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -22,7 +22,9 @@
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
 import android.app.AppCompatTaskInfo.CameraCompatControlState;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -33,6 +35,7 @@
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayLayout;
@@ -68,6 +71,8 @@
     @VisibleForTesting
     CompatUILayout mLayout;
 
+    private final float mHideScmTolerance;
+
     CompatUIWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, CompatUICallback callback,
             ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
@@ -75,11 +80,13 @@
             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mCallback = callback;
-        mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+        mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
+                && shouldShowSizeCompatRestartButton(taskInfo);
         mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
         mCompatUIHintsState = compatUIHintsState;
         mCompatUIConfiguration = compatUIConfiguration;
         mOnRestartButtonClicked = onRestartButtonClicked;
+        mHideScmTolerance = mCompatUIConfiguration.getHideSizeCompatRestartButtonTolerance();
     }
 
     @Override
@@ -107,6 +114,11 @@
         mLayout = inflateLayout();
         mLayout.inject(this);
 
+        final TaskInfo taskInfo = getLastTaskInfo();
+        if (taskInfo != null) {
+            mHasSizeCompat = mHasSizeCompat && shouldShowSizeCompatRestartButton(taskInfo);
+        }
+
         updateVisibilityOfViews();
 
         if (mHasSizeCompat) {
@@ -127,7 +139,8 @@
             boolean canShow) {
         final boolean prevHasSizeCompat = mHasSizeCompat;
         final int prevCameraCompatControlState = mCameraCompatControlState;
-        mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+        mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
+                && shouldShowSizeCompatRestartButton(taskInfo);
         mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
 
         if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
@@ -208,6 +221,30 @@
         updateSurfacePosition(positionX, positionY);
     }
 
+    @VisibleForTesting
+    boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) {
+        if (!Flags.allowHideScmButton()) {
+            return true;
+        }
+        final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+        final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+        final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth,
+                appCompatTaskInfo.topActivityLetterboxHeight);
+        final int taskArea = computeArea(taskBounds.width(), taskBounds.height());
+        if (letterboxArea == 0 || taskArea == 0) {
+            return false;
+        }
+        final float percentageAreaOfLetterboxInTask = (float) letterboxArea / taskArea * 100;
+        return percentageAreaOfLetterboxInTask < mHideScmTolerance;
+    }
+
+    private int computeArea(int width, int height) {
+        if (width == 0 || height == 0) {
+            return 0;
+        }
+        return width * height;
+    }
+
     private void updateVisibilityOfViews() {
         if (mLayout == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 95d7ad5..c3a82ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -99,7 +99,11 @@
             windowDecoration: DesktopModeWindowDecoration
     ) {
         if (inProgress) {
-            error("A drag to desktop is already in progress")
+            KtProtoLog.v(
+                    ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+                    "DragToDesktop: Drag to desktop transition already in progress."
+            )
+            return
         }
 
         val options = ActivityOptions.makeBasic().apply {
@@ -144,6 +148,12 @@
      * inside the desktop drop zone.
      */
     fun finishDragToDesktopTransition(wct: WindowContainerTransaction) {
+        if (!inProgress) {
+            // Don't attempt to finish a drag to desktop transition since there is no transition in
+            // progress which means that the drag to desktop transition was never successfully
+            // started.
+            return
+        }
         if (requireTransitionState().startAborted) {
             // Don't attempt to complete the drag-to-desktop since the start transition didn't
             // succeed as expected. Just reset the state as if nothing happened.
@@ -161,6 +171,12 @@
      * means the user wants to remain in their current windowing mode.
      */
     fun cancelDragToDesktopTransition() {
+        if (!inProgress) {
+            // Don't attempt to cancel a drag to desktop transition since there is no transition in
+            // progress which means that the drag to desktop transition was never successfully
+            // started.
+            return
+        }
         val state = requireTransitionState()
         if (state.startAborted) {
             // Don't attempt to cancel the drag-to-desktop since the start transition didn't
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index b0d8b47..3fb0dbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -83,6 +83,9 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.tracing.LegacyTransitionTracer;
+import com.android.wm.shell.transition.tracing.PerfettoTransitionTracer;
+import com.android.wm.shell.transition.tracing.TransitionTracer;
 import com.android.wm.shell.util.TransitionUtil;
 
 import java.io.PrintWriter;
@@ -184,7 +187,7 @@
     private final ShellController mShellController;
     private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
     private final SleepHandler mSleepHandler = new SleepHandler();
-    private final Tracer mTracer = new Tracer();
+    private final TransitionTracer mTransitionTracer;
     private boolean mIsRegistered = false;
 
     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -307,6 +310,12 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
         shellInit.addInitCallback(this::onInit, this);
         mHomeTransitionObserver = observer;
+
+        if (android.tracing.Flags.perfettoTransitionTracing()) {
+            mTransitionTracer = new PerfettoTransitionTracer();
+        } else {
+            mTransitionTracer = new LegacyTransitionTracer();
+        }
     }
 
     private void onInit() {
@@ -868,7 +877,7 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
                 + " %s is still animating. Notify the animating transition"
                 + " in case they can be merged", ready, playing);
-        mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
+        mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
         playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
                 playing.mToken, (wct) -> onMerged(playing, ready));
     }
@@ -902,7 +911,7 @@
         for (int i = 0; i < mObservers.size(); ++i) {
             mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
         }
-        mTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
+        mTransitionTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
         // See if we should merge another transition.
         processReadyQueue(track);
     }
@@ -923,7 +932,7 @@
                     active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct));
             if (consumed) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
-                mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
+                mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
                 return;
             }
         }
@@ -948,7 +957,7 @@
             if (consumed) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
                         mHandlers.get(i));
-                mTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
+                mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
                 return mHandlers.get(i);
             }
         }
@@ -978,7 +987,7 @@
         final Track track = mTracks.get(transition.getTrack());
         transition.mAborted = true;
 
-        mTracer.logAborted(transition.mInfo.getDebugId());
+        mTransitionTracer.logAborted(transition.mInfo.getDebugId());
 
         if (transition.mHandler != null) {
             // Notifies to clean-up the aborted transition.
@@ -1506,12 +1515,18 @@
         }
     }
 
-
     @Override
     public boolean onShellCommand(String[] args, PrintWriter pw) {
         switch (args[0]) {
             case "tracing": {
-                mTracer.onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+                if (!android.tracing.Flags.perfettoTransitionTracing()) {
+                    ((LegacyTransitionTracer) mTransitionTracer)
+                            .onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+                } else {
+                    pw.println("Command not supported. Use the Perfetto command instead to start "
+                            + "and stop this trace instead.");
+                    return false;
+                }
                 return true;
             }
             default: {
@@ -1524,8 +1539,10 @@
 
     @Override
     public void printShellCommandHelp(PrintWriter pw, String prefix) {
-        pw.println(prefix + "tracing");
-        mTracer.printShellCommandHelp(pw, prefix + "  ");
+        if (!android.tracing.Flags.perfettoTransitionTracing()) {
+            pw.println(prefix + "tracing");
+            ((LegacyTransitionTracer) mTransitionTracer).printShellCommandHelp(pw, prefix + "  ");
+        }
     }
 
     private void dump(@NonNull PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
similarity index 88%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
index 5919aad..9c84886 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.transition;
+package com.android.wm.shell.transition.tracing;
 
 import static android.os.Build.IS_USER;
 
@@ -29,6 +29,7 @@
 
 import com.android.internal.util.TraceBuffer;
 import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.transition.Transitions;
 
 import com.google.protobuf.nano.MessageNano;
 
@@ -45,7 +46,8 @@
 /**
  * Helper class to collect and dump transition traces.
  */
-public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
+public class LegacyTransitionTracer
+        implements ShellCommandHandler.ShellCommandActionHandler, TransitionTracer {
     private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
     private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
 
@@ -60,33 +62,33 @@
 
     private final TraceBuffer.ProtoProvider mProtoProvider =
             new TraceBuffer.ProtoProvider<MessageNano,
-                com.android.wm.shell.nano.WmShellTransitionTraceProto,
-                com.android.wm.shell.nano.Transition>() {
-        @Override
-        public int getItemSize(MessageNano proto) {
-            return proto.getCachedSize();
-        }
+                    com.android.wm.shell.nano.WmShellTransitionTraceProto,
+                    com.android.wm.shell.nano.Transition>() {
+                @Override
+                public int getItemSize(MessageNano proto) {
+                    return proto.getCachedSize();
+                }
 
-        @Override
-        public byte[] getBytes(MessageNano proto) {
-            return MessageNano.toByteArray(proto);
-        }
+                @Override
+                public byte[] getBytes(MessageNano proto) {
+                    return MessageNano.toByteArray(proto);
+                }
 
-        @Override
-        public void write(
-                com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
-                Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
+                @Override
+                public void write(
+                        com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
+                        Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
                         throws IOException {
-            encapsulatingProto.transitions = buffer.toArray(
-                    new com.android.wm.shell.nano.Transition[0]);
-            os.write(getBytes(encapsulatingProto));
-        }
-    };
+                    encapsulatingProto.transitions = buffer.toArray(
+                            new com.android.wm.shell.nano.Transition[0]);
+                    os.write(getBytes(encapsulatingProto));
+                }
+            };
     private final TraceBuffer<MessageNano,
             com.android.wm.shell.nano.WmShellTransitionTraceProto,
-            com.android.wm.shell.nano.Transition> mTraceBuffer
-                    = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
-                            (proto) -> handleOnEntryRemovedFromTrace(proto));
+            com.android.wm.shell.nano.Transition> mTraceBuffer =
+                new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
+                        this::handleOnEntryRemovedFromTrace);
     private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>();
 
     private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>();
@@ -99,6 +101,7 @@
      * @param transitionId The id of the transition being dispatched.
      * @param handler The handler the transition is being dispatched to.
      */
+    @Override
     public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
         final int handlerId;
         if (mHandlerIds.containsKey(handler)) {
@@ -130,6 +133,7 @@
      *
      * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
      */
+    @Override
     public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
         com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
         proto.id = mergeRequestedTransitionId;
@@ -145,6 +149,7 @@
      * @param mergedTransitionId The id of the transition that was merged.
      * @param playingTransitionId The id of the transition the transition was merged into.
      */
+    @Override
     public void logMerged(int mergedTransitionId, int playingTransitionId) {
         com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
         proto.id = mergedTransitionId;
@@ -159,6 +164,7 @@
      *
      * @param transitionId The id of the transition that was aborted.
      */
+    @Override
     public void logAborted(int transitionId) {
         com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
         proto.id = transitionId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
new file mode 100644
index 0000000..99df6a3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition.tracing;
+
+import android.internal.perfetto.protos.PerfettoTrace;
+import android.os.SystemClock;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.tracing.perfetto.TracingContext;
+import android.tracing.transition.TransitionDataSource;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Helper class to collect and dump transition traces.
+ */
+public class PerfettoTransitionTracer implements TransitionTracer {
+    private final AtomicInteger mActiveTraces = new AtomicInteger(0);
+    private final TransitionDataSource mDataSource = new TransitionDataSource(
+            mActiveTraces::incrementAndGet,
+            this::onFlush,
+            mActiveTraces::decrementAndGet);
+
+    public PerfettoTransitionTracer() {
+        Producer.init(InitArguments.DEFAULTS);
+        mDataSource.register(DataSourceParams.DEFAULTS);
+    }
+
+    /**
+     * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+     *
+     * @param transitionId The id of the transition being dispatched.
+     * @param handler The handler the transition is being dispatched to.
+     */
+    @Override
+    public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace(ctx -> {
+            final int handlerId = getHandlerId(handler, ctx);
+
+            final ProtoOutputStream os = ctx.newTracePacket();
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, transitionId);
+            os.write(PerfettoTrace.ShellTransition.DISPATCH_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            os.write(PerfettoTrace.ShellTransition.HANDLER, handlerId);
+            os.end(token);
+        });
+    }
+
+    private static int getHandlerId(Transitions.TransitionHandler handler,
+            TracingContext<DataSourceInstance, TransitionDataSource.TlsState, Void> ctx) {
+        final Map<String, Integer> handlerMapping =
+                ctx.getCustomTlsState().handlerMapping;
+        final int handlerId;
+        if (handlerMapping.containsKey(handler.getClass().getName())) {
+            handlerId = handlerMapping.get(handler.getClass().getName());
+        } else {
+            // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+            handlerId = handlerMapping.size() + 1;
+            handlerMapping.put(handler.getClass().getName(), handlerId);
+        }
+        return handlerId;
+    }
+
+    /**
+     * Adds an entry in the trace to log that a request to merge a transition was made.
+     *
+     * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+     */
+    @Override
+    public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace(ctx -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
+            os.write(PerfettoTrace.ShellTransition.MERGE_REQUEST_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
+            os.end(token);
+        });
+    }
+
+    /**
+     * Adds an entry in the trace to log that a transition was merged by the handler.
+     *
+     * @param mergedTransitionId The id of the transition that was merged.
+     * @param playingTransitionId The id of the transition the transition was merged into.
+     */
+    @Override
+    public void logMerged(int mergedTransitionId, int playingTransitionId) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace(ctx -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId);
+            os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
+            os.end(token);
+        });
+    }
+
+    /**
+     * Adds an entry in the trace to log that a transition was aborted.
+     *
+     * @param transitionId The id of the transition that was aborted.
+     */
+    @Override
+    public void logAborted(int transitionId) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace(ctx -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, transitionId);
+            os.write(PerfettoTrace.ShellTransition.SHELL_ABORT_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            os.end(token);
+        });
+    }
+
+    private boolean isTracing() {
+        return mActiveTraces.get() > 0;
+    }
+
+    private void onFlush() {
+        mDataSource.trace(ctx -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+
+            final Map<String, Integer> handlerMapping = ctx.getCustomTlsState().handlerMapping;
+            for (String handler : handlerMapping.keySet()) {
+                final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
+                os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerMapping.get(handler));
+                os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
+                os.end(token);
+            }
+
+            ctx.flush();
+        });
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
new file mode 100644
index 0000000..5857ad8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition.tracing;
+
+import com.android.wm.shell.transition.Transitions;
+
+public interface TransitionTracer {
+    /**
+     * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+     *
+     * @param transitionId The id of the transition being dispatched.
+     * @param handler The handler the transition is being dispatched to.
+     */
+    void logDispatched(int transitionId, Transitions.TransitionHandler handler);
+
+    /**
+     * Adds an entry in the trace to log that a request to merge a transition was made.
+     *
+     * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+     */
+    void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId);
+
+    /**
+     * Adds an entry in the trace to log that a transition was merged by the handler.
+     *
+     * @param mergedTransitionId The id of the transition that was merged.
+     * @param playingTransitionId The id of the transition the transition was merged into.
+     */
+    void logMerged(int mergedTransitionId, int playingTransitionId);
+
+    /**
+     * Adds an entry in the trace to log that a transition was aborted.
+     *
+     * @param transitionId The id of the transition that was aborted.
+     */
+    void logAborted(int transitionId);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index 4aed7c4..e6d35e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.unfold;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-
 import android.annotation.NonNull;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
@@ -230,8 +228,7 @@
     }
 
     private void maybeResetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
-        // TODO(b/311084698): the windowing mode check is added here as a work around.
-        if (!mIsInStageChange || taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+        if (!mIsInStageChange) {
             // No need to resetTask if there is no ongoing state change.
             return;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 1a793a1..b2eeea7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -271,6 +271,9 @@
                     if (e.findPointerIndex(mDragPointerId) == -1) {
                         mDragPointerId = e.getPointerId(0);
                     }
+                    final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+                    // If a decor's resize drag zone is active, don't also try to reposition it.
+                    if (decoration.isHandlingDragResize()) break;
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 1debb02..5a74255 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -286,6 +286,10 @@
         closeBackground.setTintList(buttonTintColor);
     }
 
+    boolean isHandlingDragResize() {
+        return mDragResizeListener.isHandlingDragResize();
+    }
+
     private void closeDragResizeListener() {
         if (mDragResizeListener == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index aabc1cf..554b1fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -491,8 +491,11 @@
                     return true;
                 }
                 case MotionEvent.ACTION_MOVE: {
+                    mShouldClick = false;
                     final DesktopModeWindowDecoration decoration =
                             mWindowDecorByTaskId.get(mTaskId);
+                    // If a decor's resize drag zone is active, don't also try to reposition it.
+                    if (decoration.isHandlingDragResize()) break;
                     decoration.closeMaximizeMenu();
                     if (e.findPointerIndex(mDragPointerId) == -1) {
                         mDragPointerId = e.getPointerId(0);
@@ -505,7 +508,6 @@
                             e.getRawX(dragPointerIdx),
                             newTaskBounds));
                     mIsDragging = true;
-                    mShouldClick = false;
                     return true;
                 }
                 case MotionEvent.ACTION_UP:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 53f806c..2023333 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -387,6 +387,10 @@
         return mHandleMenu != null;
     }
 
+    boolean isHandlingDragResize() {
+        return mDragResizeListener.isHandlingDragResize();
+    }
+
     private void loadAppInfo() {
         String packageName = mTaskInfo.realActivity.getPackageName();
         PackageManager pm = mContext.getApplicationContext().getPackageManager();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8511a21..8b38f99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -320,6 +320,10 @@
         }
     }
 
+    boolean isHandlingDragResize() {
+        return mInputEventReceiver.isHandlingEvents();
+    }
+
     @Override
     public void close() {
         mInputEventReceiver.dispose();
@@ -386,6 +390,10 @@
             finishInputEvent(inputEvent, handleInputEvent(inputEvent));
         }
 
+        boolean isHandlingEvents() {
+            return mShouldHandleEvents;
+        }
+
         private boolean handleInputEvent(InputEvent inputEvent) {
             if (!(inputEvent instanceof MotionEvent)) {
                 return false;
@@ -409,7 +417,6 @@
                         mShouldHandleEvents = isInResizeHandleBounds(x, y);
                     }
                     if (mShouldHandleEvents) {
-                        mInputManager.pilferPointers(mInputChannel.getToken());
                         mDragPointerId = e.getPointerId(0);
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
@@ -427,6 +434,7 @@
                     if (!mShouldHandleEvents) {
                         break;
                     }
+                    mInputManager.pilferPointers(mInputChannel.getToken());
                     int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                     float rawX = e.getRawX(dragPointerIndex);
                     float rawY = e.getRawY(dragPointerIndex);
@@ -437,6 +445,7 @@
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
+                    mInputManager.pilferPointers(mInputChannel.getToken());
                     if (mShouldHandleEvents) {
                         int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                         final Rect taskBounds = mCallback.onDragPositioningEnd(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index c1b18f9..7c6e69e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -61,6 +61,7 @@
     private int mCtrlType;
     private boolean mIsResizingOrAnimatingResize;
     @Surface.Rotation private int mRotation;
+    private boolean mVeilIsVisible;
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration,
@@ -94,7 +95,6 @@
                 mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
         mRepositionStartPoint.set(x, y);
         if (isResizing()) {
-            mDesktopWindowDecoration.showResizeVeil(mTaskBoundsAtDragStart);
             if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
                 WindowContainerTransaction wct = new WindowContainerTransaction();
                 wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -119,8 +119,13 @@
         if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
                 mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
                 mDisplayController, mDesktopWindowDecoration)) {
-            mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
             mIsResizingOrAnimatingResize = true;
+            if (!mVeilIsVisible) {
+                mDesktopWindowDecoration.showResizeVeil(mRepositionTaskBounds);
+                mVeilIsVisible = true;
+            } else {
+                mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+            }
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
@@ -143,7 +148,7 @@
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
                 wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
                 mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
-            } else {
+            } else if (mVeilIsVisible) {
                 // If bounds haven't changed, perform necessary veil reset here as startAnimation
                 // won't be called.
                 mDesktopWindowDecoration.hideResizeVeil();
@@ -163,6 +168,7 @@
         mCtrlType = CTRL_TYPE_UNDEFINED;
         mTaskBoundsAtDragStart.setEmpty();
         mRepositionStartPoint.set(0, 0);
+        mVeilIsVisible = false;
         return new Rect(mRepositionTaskBounds);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index d4b97ed..2acfd83 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,6 +20,8 @@
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.WindowInsets.Type.navigationBars;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -27,6 +29,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
@@ -39,6 +42,7 @@
 import android.app.TaskInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.util.Pair;
 import android.view.DisplayInfo;
@@ -50,6 +54,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
@@ -59,6 +64,7 @@
 import junit.framework.Assert;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -76,6 +82,8 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class CompatUIWindowManagerTest extends ShellTestCase {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
     private static final int TASK_ID = 1;
 
@@ -107,6 +115,7 @@
     public void testCreateSizeCompatButton() {
         // Doesn't create layout if show is false.
         mWindowManager.mHasSizeCompat = true;
+        doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
         assertTrue(mWindowManager.createLayout(/* canShow= */ false));
 
         verify(mWindowManager, never()).inflateLayout();
@@ -199,6 +208,7 @@
         // No diff
         clearInvocations(mWindowManager);
         TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+        doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
         assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
 
         verify(mWindowManager, never()).updateSurfacePosition();
@@ -284,6 +294,7 @@
     @Test
     public void testUpdateCompatInfoLayoutNotInflatedYet() {
         mWindowManager.mHasSizeCompat = true;
+        doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
         mWindowManager.createLayout(/* canShow= */ false);
 
         verify(mWindowManager, never()).inflateLayout();
@@ -353,6 +364,7 @@
         // Create button if it is not created.
         mWindowManager.mLayout = null;
         mWindowManager.mHasSizeCompat = true;
+        doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
         mWindowManager.updateVisibility(/* canShow= */ true);
 
         verify(mWindowManager).createLayout(/* canShow= */ true);
@@ -464,6 +476,37 @@
         Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
     }
 
+    @Test
+    public void testShouldShowSizeCompatRestartButton() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
+
+        doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
+        mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+                mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+                mCompatUIConfiguration, mOnRestartButtonClicked);
+
+        // Simulate rotation of activity in square display
+        TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
+        taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
+
+        assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+        // Simulate exiting split screen/folding
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+        assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+        // Simulate folding
+        taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000));
+        assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500;
+        assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+    }
+
     private static TaskInfo createTaskInfo(boolean hasSizeCompat,
             @AppCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) {
         ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 3bc90ad..be639e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -34,6 +34,7 @@
 import org.mockito.Mock
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
+import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyZeroInteractions
 import org.mockito.kotlin.whenever
@@ -150,6 +151,23 @@
     }
 
     @Test
+    fun startDragToDesktop_anotherTransitionInProgress_startDropped() {
+        val task = createTask()
+        val dragAnimator = mock<MoveToDesktopAnimator>()
+
+        // Simulate attempt to start two drag to desktop transitions.
+        startDragToDesktopTransition(task, dragAnimator)
+        startDragToDesktopTransition(task, dragAnimator)
+
+        // Verify transition only started once.
+        verify(transitions, times(1)).startTransition(
+                eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
+                any(),
+                eq(handler)
+        )
+    }
+
+    @Test
     fun cancelDragToDesktop_startWasReady_cancel() {
         val task = createTask()
         val dragAnimator = mock<MoveToDesktopAnimator>()
@@ -189,6 +207,32 @@
         verifyZeroInteractions(dragAnimator)
     }
 
+    @Test
+    fun cancelDragToDesktop_transitionNotInProgress_dropCancel() {
+        // Then cancel is called before the transition was started.
+        handler.cancelDragToDesktopTransition()
+
+        // Verify cancel is dropped.
+        verify(transitions, never()).startTransition(
+                eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+                any(),
+                eq(handler)
+        )
+    }
+
+    @Test
+    fun finishDragToDesktop_transitionNotInProgress_dropFinish() {
+        // Then finish is called before the transition was started.
+        handler.finishDragToDesktopTransition(WindowContainerTransaction())
+
+        // Verify finish is dropped.
+        verify(transitions, never()).startTransition(
+                eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
+                any(),
+                eq(handler)
+        )
+    }
+
     private fun startDragToDesktopTransition(
         task: RunningTaskInfo,
         dragAnimator: MoveToDesktopAnimator
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 0841210..86253f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -144,13 +144,13 @@
     }
 
     @Test
-    fun testDragResize_noMove_showsResizeVeil() {
+    fun testDragResize_noMove_doesNotShowResizeVeil() {
         taskPositioner.onDragPositioningStart(
             CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
             STARTING_BOUNDS.left.toFloat(),
             STARTING_BOUNDS.top.toFloat()
         )
-        verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
+        verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
 
         taskPositioner.onDragPositioningEnd(
             STARTING_BOUNDS.left.toFloat(),
@@ -162,7 +162,7 @@
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
                         change.configuration.windowConfiguration.bounds == STARTING_BOUNDS}},
             eq(taskPositioner))
-        verify(mockDesktopWindowDecoration).hideResizeVeil()
+        verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
     }
 
     @Test
@@ -212,7 +212,6 @@
             STARTING_BOUNDS.right.toFloat(),
             STARTING_BOUNDS.top.toFloat()
         )
-        verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
 
         taskPositioner.onDragPositioningMove(
             STARTING_BOUNDS.right.toFloat() + 10,
@@ -222,6 +221,7 @@
         val rectAfterMove = Rect(STARTING_BOUNDS)
         rectAfterMove.right += 10
         rectAfterMove.top += 10
+        verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove)
         verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
@@ -237,7 +237,7 @@
         val rectAfterEnd = Rect(rectAfterMove)
         rectAfterEnd.right += 10
         rectAfterEnd.top += 10
-        verify(mockDesktopWindowDecoration, times(2)).updateResizeVeil(any())
+        verify(mockDesktopWindowDecoration).updateResizeVeil(any())
         verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
@@ -253,7 +253,6 @@
             STARTING_BOUNDS.left.toFloat(),
             STARTING_BOUNDS.top.toFloat()
         )
-        verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
 
         taskPositioner.onDragPositioningMove(
             STARTING_BOUNDS.left.toFloat(),
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index c9d5e07..d9166a1 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -21,6 +21,7 @@
 #include <algorithm>
 #include <cstddef>
 #include <limits>
+#include <optional>
 
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
@@ -50,7 +51,9 @@
 // contiguous block of memory to store both the TypeSpec struct and
 // the Type structs.
 struct TypeSpecBuilder {
-  explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {}
+  explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {
+    type_entries.reserve(dtohs(header_->typesCount));
+  }
 
   void AddType(incfs::verified_map_ptr<ResTable_type> type) {
     TypeSpec::TypeEntry& entry = type_entries.emplace_back();
@@ -59,6 +62,7 @@
   }
 
   TypeSpec Build() {
+    type_entries.shrink_to_fit();
     return {header_, std::move(type_entries)};
   }
 
@@ -450,7 +454,8 @@
 std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
                                                          package_property_t property_flags) {
   ATRACE_NAME("LoadedPackage::Load");
-  std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
+  const bool optimize_name_lookups = (property_flags & PROPERTY_OPTIMIZE_NAME_LOOKUPS) != 0;
+  std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage(optimize_name_lookups));
 
   // typeIdOffset was added at some point, but we still must recognize apps built before this
   // was added.
@@ -499,7 +504,7 @@
   // A map of TypeSpec builders, each associated with an type index.
   // We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
   // contiguous block of memory that holds all the Types together with the TypeSpec.
-  std::unordered_map<int, std::unique_ptr<TypeSpecBuilder>> type_builder_map;
+  std::unordered_map<int, std::optional<TypeSpecBuilder>> type_builder_map;
 
   ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
   while (iter.HasNext()) {
@@ -567,14 +572,14 @@
           return {};
         }
 
-        if (entry_count * sizeof(uint32_t) > chunk.data_size()) {
+        if (entry_count * sizeof(uint32_t) > child_chunk.data_size()) {
           LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small to hold entries.";
           return {};
         }
 
-        std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type_spec->id];
-        if (builder_ptr == nullptr) {
-          builder_ptr = util::make_unique<TypeSpecBuilder>(type_spec.verified());
+        auto& maybe_type_builder = type_builder_map[type_spec->id];
+        if (!maybe_type_builder) {
+          maybe_type_builder.emplace(type_spec.verified());
           loaded_package->resource_ids_.set(type_spec->id, entry_count);
         } else {
           LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x",
@@ -594,9 +599,9 @@
         }
 
         // Type chunks must be preceded by their TypeSpec chunks.
-        std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type->id];
-        if (builder_ptr != nullptr) {
-          builder_ptr->AddType(type.verified());
+        auto& maybe_type_builder = type_builder_map[type->id];
+        if (maybe_type_builder) {
+          maybe_type_builder->AddType(type.verified());
         } else {
           LOG(ERROR) << StringPrintf(
               "RES_TABLE_TYPE_TYPE with ID %02x found without preceding RES_TABLE_TYPE_SPEC_TYPE.",
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 4c992be..2c99f1a 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -447,15 +447,19 @@
 // --------------------------------------------------------------------
 // --------------------------------------------------------------------
 
-ResStringPool::ResStringPool()
-    : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
+ResStringPool::ResStringPool() : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL) {
 }
 
-ResStringPool::ResStringPool(const void* data, size_t size, bool copyData)
-    : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
-    setTo(data, size, copyData);
+ResStringPool::ResStringPool(bool optimize_name_lookups) : ResStringPool() {
+  if (optimize_name_lookups) {
+    mIndexLookupCache.emplace();
+  }
+}
+
+ResStringPool::ResStringPool(const void* data, size_t size, bool copyData,
+                             bool optimize_name_lookups)
+    : ResStringPool(optimize_name_lookups) {
+  setTo(data, size, copyData);
 }
 
 ResStringPool::~ResStringPool()
@@ -683,6 +687,14 @@
         mStylePoolSize = 0;
     }
 
+    if (mIndexLookupCache) {
+      if ((mHeader->flags & ResStringPool_header::UTF8_FLAG) != 0) {
+        mIndexLookupCache->first.reserve(mHeader->stringCount);
+      } else {
+        mIndexLookupCache->second.reserve(mHeader->stringCount);
+      }
+    }
+
     return (mError=NO_ERROR);
 }
 
@@ -708,6 +720,10 @@
         free(mOwnedData);
         mOwnedData = NULL;
     }
+    if (mIndexLookupCache) {
+      mIndexLookupCache->first.clear();
+      mIndexLookupCache->second.clear();
+    }
 }
 
 /**
@@ -824,11 +840,11 @@
 
                 // encLen must be less than 0x7FFF due to encoding.
                 if ((uint32_t)(u8str+*u8len-strings) < mStringPoolSize) {
-                    AutoMutex lock(mDecodeLock);
+                  AutoMutex lock(mCachesLock);
 
-                    if (mCache != NULL && mCache[idx] != NULL) {
-                        return StringPiece16(mCache[idx], *u16len);
-                    }
+                  if (mCache != NULL && mCache[idx] != NULL) {
+                    return StringPiece16(mCache[idx], *u16len);
+                  }
 
                     // Retrieve the actual length of the utf8 string if the
                     // encoded length was truncated
@@ -1093,12 +1109,24 @@
             // block, start searching at the back.
             String8 str8(str, strLen);
             const size_t str8Len = str8.size();
+            std::optional<AutoMutex> cacheLock;
+            if (mIndexLookupCache) {
+              cacheLock.emplace(mCachesLock);
+              if (auto it = mIndexLookupCache->first.find(std::string_view(str8));
+                  it != mIndexLookupCache->first.end()) {
+                return it->second;
+              }
+            }
+
             for (int i=mHeader->stringCount-1; i>=0; i--) {
                 const base::expected<StringPiece, NullOrIOError> s = string8At(i);
                 if (UNLIKELY(IsIOError(s))) {
                     return base::unexpected(s.error());
                 }
                 if (s.has_value()) {
+                  if (mIndexLookupCache) {
+                    mIndexLookupCache->first.insert({*s, i});
+                  }
                     if (kDebugStringPoolNoisy) {
                         ALOGI("Looking at %s, i=%d\n", s->data(), i);
                     }
@@ -1151,20 +1179,32 @@
             // most often this happens because we want to get IDs for style
             // span tags; since those always appear at the end of the string
             // block, start searching at the back.
+            std::optional<AutoMutex> cacheLock;
+            if (mIndexLookupCache) {
+              cacheLock.emplace(mCachesLock);
+              if (auto it = mIndexLookupCache->second.find({str, strLen});
+                  it != mIndexLookupCache->second.end()) {
+                return it->second;
+              }
+            }
             for (int i=mHeader->stringCount-1; i>=0; i--) {
                 const base::expected<StringPiece16, NullOrIOError> s = stringAt(i);
                 if (UNLIKELY(IsIOError(s))) {
                     return base::unexpected(s.error());
                 }
                 if (kDebugStringPoolNoisy) {
-                    ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
+                  ALOGI("Looking16 at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
                 }
-                if (s.has_value() && strLen == s->size() &&
-                        strzcmp16(s->data(), s->size(), str, strLen) == 0) {
+                if (s.has_value()) {
+                  if (mIndexLookupCache) {
+                    mIndexLookupCache->second.insert({*s, i});
+                  }
+                  if (strLen == s->size() && strzcmp16(s->data(), s->size(), str, strLen) == 0) {
                     if (kDebugStringPoolNoisy) {
-                        ALOGI("MATCH!");
+                      ALOGI("MATCH16!");
                     }
                     return i;
+                  }
                 }
             }
         }
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 6068912..d9f7c2a 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -71,7 +71,7 @@
 
   // Rewrites a compile-time overlay resource id to the runtime resource id of corresponding target
   // resource.
-  virtual status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
+  status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
 
   const Idmap_data_header* data_header_;
   const Idmap_overlay_entry* entries_;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 3a72871..413b278 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,6 +99,9 @@
 
   // The apk assets only contain the overlayable declarations information.
   PROPERTY_ONLY_OVERLAYABLES = 1U << 5U,
+
+  // Optimize the resource lookups by name via an in-memory lookup table.
+  PROPERTY_OPTIMIZE_NAME_LOOKUPS = 1U << 6U,
 };
 
 struct OverlayableInfo {
@@ -285,7 +288,9 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
 
-  LoadedPackage() = default;
+  explicit LoadedPackage(bool optimize_name_lookups = false)
+      : type_string_pool_(optimize_name_lookups), key_string_pool_(optimize_name_lookups) {
+  }
 
   ResStringPool type_string_pool_;
   ResStringPool key_string_pool_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index c0514fd..3d1403d 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -22,27 +22,28 @@
 
 #include <android-base/expected.h>
 #include <android-base/unique_fd.h>
-
+#include <android/configuration.h>
 #include <androidfw/Asset.h>
 #include <androidfw/Errors.h>
 #include <androidfw/LocaleData.h>
 #include <androidfw/StringPiece.h>
 #include <utils/ByteOrder.h>
 #include <utils/Errors.h>
+#include <utils/KeyedVector.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
-#include <utils/KeyedVector.h>
-
 #include <utils/threads.h>
 
 #include <stdint.h>
 #include <sys/types.h>
 
-#include <android/configuration.h>
-
 #include <array>
 #include <map>
 #include <memory>
+#include <optional>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
 
 namespace android {
 
@@ -512,23 +513,24 @@
 class ResStringPool
 {
 public:
-    ResStringPool();
-    ResStringPool(const void* data, size_t size, bool copyData=false);
-    virtual ~ResStringPool();
+ ResStringPool();
+ explicit ResStringPool(bool optimize_name_lookups);
+ ResStringPool(const void* data, size_t size, bool copyData = false,
+               bool optimize_name_lookups = false);
+ virtual ~ResStringPool();
 
-    void setToEmpty();
-    status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData=false);
+ void setToEmpty();
+ status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData = false);
 
-    status_t getError() const;
+ status_t getError() const;
 
-    void uninit();
+ void uninit();
 
-    // Return string entry as UTF16; if the pool is UTF8, the string will
-    // be converted before returning.
-    inline base::expected<StringPiece16, NullOrIOError> stringAt(
-            const ResStringPool_ref& ref) const {
-        return stringAt(ref.index);
-    }
+ // Return string entry as UTF16; if the pool is UTF8, the string will
+ // be converted before returning.
+ inline base::expected<StringPiece16, NullOrIOError> stringAt(const ResStringPool_ref& ref) const {
+   return stringAt(ref.index);
+ }
     virtual base::expected<StringPiece16, NullOrIOError> stringAt(size_t idx) const;
 
     // Note: returns null if the string pool is not UTF8.
@@ -557,7 +559,7 @@
     void*                                         mOwnedData;
     incfs::verified_map_ptr<ResStringPool_header> mHeader;
     size_t                                        mSize;
-    mutable Mutex                                 mDecodeLock;
+    mutable Mutex                                 mCachesLock;
     incfs::map_ptr<uint32_t>                      mEntries;
     incfs::map_ptr<uint32_t>                      mEntryStyles;
     incfs::map_ptr<void>                          mStrings;
@@ -566,6 +568,10 @@
     incfs::map_ptr<uint32_t>                      mStyles;
     uint32_t                                      mStylePoolSize;    // number of uint32_t
 
+    mutable std::optional<std::pair<std::unordered_map<std::string_view, int>,
+                                    std::unordered_map<std::u16string_view, int>>>
+        mIndexLookupCache;
+
     base::expected<StringPiece, NullOrIOError> stringDecodeAt(
         size_t idx, incfs::map_ptr<uint8_t> str, size_t encLen) const;
 };
@@ -1401,8 +1407,8 @@
     
     // Must be 0.
     uint8_t res0;
-    // Must be 0.
-    uint16_t res1;
+    // Used to be reserved, if >0 specifies the number of ResTable_type entries for this spec.
+    uint16_t typesCount;
     
     // Number of uint32_t entry configuration masks that follow.
     uint32_t entryCount;
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 00d049c..6ebfc63 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,6 +41,14 @@
 #endif  // __ANDROID__
 }
 
+inline bool inter_character_justification() {
+#ifdef __ANDROID__
+    return com_android_text_flags_inter_character_justification();
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
 }  // namespace text_feature
 
 }  // namespace android
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index f4ee36ec..bbb1420 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -131,11 +131,6 @@
         const std::vector<minikin::FontVariation>& variations) const {
     SkFontArguments args;
 
-    int ttcIndex;
-    std::unique_ptr<SkStreamAsset> stream(mTypeface->openStream(&ttcIndex));
-    LOG_ALWAYS_FATAL_IF(stream == nullptr, "openStream failed");
-
-    args.setCollectionIndex(ttcIndex);
     std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation;
     skVariation.resize(variations.size());
     for (size_t i = 0; i < variations.size(); i++) {
@@ -143,11 +138,10 @@
         skVariation[i].value = SkFloatToScalar(variations[i].value);
     }
     args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
-    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
-    sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
+    sk_sp<SkTypeface> face = mTypeface->makeClone(args);
 
     return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
-                                             mFilePath, ttcIndex, variations);
+                                             mFilePath, mTtcIndex, variations);
 }
 
 // hinting<<16 | edging<<8 | bools:5bits
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 833069f..5613369 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,10 +72,13 @@
     const minikin::Range contextRange(contextStart, contextStart + contextCount);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+                                                    ? paint->getRunFlag()
+                                                    : minikin::RunFlag::NONE;
 
     if (mt == nullptr) {
         return minikin::Layout(textBuf.substr(contextRange), range - contextStart, bidiFlags,
-                               minikinPaint, startHyphen, endHyphen);
+                               minikinPaint, startHyphen, endHyphen, minikinRunFlag);
     } else {
         return mt->buildLayout(textBuf, range, contextRange, minikinPaint, startHyphen, endHyphen);
     }
@@ -102,9 +105,12 @@
     const minikin::Range range(start, start + count);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+                                                    ? paint->getRunFlag()
+                                                    : minikin::RunFlag::NONE;
 
     return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
-                                        endHyphen, advances, bounds, clusterCount);
+                                        endHyphen, advances, bounds, clusterCount, minikinRunFlag);
 }
 
 minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index ef4dce5..708f96e5 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -25,6 +25,7 @@
 #include <minikin/FontFamily.h>
 #include <minikin/FontFeature.h>
 #include <minikin/Hyphenator.h>
+#include <minikin/Layout.h>
 
 #include <string>
 
@@ -144,6 +145,9 @@
     bool isDevKern() const { return mDevKern; }
     void setDevKern(bool d) { mDevKern = d; }
 
+    minikin::RunFlag getRunFlag() const { return mRunFlag; }
+    void setRunFlag(minikin::RunFlag runFlag) { mRunFlag = runFlag; }
+
     // Deprecated -- bitmapshaders will be taking this flag explicitly
     bool isFilterBitmap() const { return mFilterBitmap; }
     void setFilterBitmap(bool filter) { mFilterBitmap = filter; }
@@ -188,6 +192,7 @@
     bool mStrikeThru = false;
     bool mUnderline = false;
     bool mDevKern = false;
+    minikin::RunFlag mRunFlag = minikin::RunFlag::NONE;
 };
 
 }  // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index aac928f..c32ea01 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -47,8 +47,8 @@
         , mFilterBitmap(paint.mFilterBitmap)
         , mStrikeThru(paint.mStrikeThru)
         , mUnderline(paint.mUnderline)
-        , mDevKern(paint.mDevKern) {}
-
+        , mDevKern(paint.mDevKern)
+        , mRunFlag(paint.mRunFlag) {}
 
 Paint::~Paint() {}
 
@@ -68,21 +68,19 @@
     mStrikeThru = other.mStrikeThru;
     mUnderline = other.mUnderline;
     mDevKern = other.mDevKern;
+    mRunFlag = other.mRunFlag;
     return *this;
 }
 
 bool operator==(const Paint& a, const Paint& b) {
-    return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
-           a.mFont == b.mFont &&
-           a.mLooper == b.mLooper && 
-           a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
-           a.mFontFeatureSettings == b.mFontFeatureSettings &&
+    return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont &&
+           a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing &&
+           a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings &&
            a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
            a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
            a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
-           a.mFilterBitmap == b.mFilterBitmap &&
-           a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
-           a.mDevKern == b.mDevKern;
+           a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru &&
+           a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag;
 }
 
 void Paint::reset() {
@@ -96,6 +94,7 @@
     mStrikeThru = false;
     mUnderline = false;
     mDevKern = false;
+    mRunFlag = minikin::RunFlag::NONE;
 }
 
 void Paint::setLooper(sk_sp<BlurDrawLooper> looper) {
@@ -133,6 +132,8 @@
 // flags related to minikin::Paint
 static const uint32_t sUnderlineFlag    = 0x08;
 static const uint32_t sStrikeThruFlag   = 0x10;
+static const uint32_t sTextRunLeftEdge = 0x2000;
+static const uint32_t sTextRunRightEdge = 0x4000;
 // flags no longer supported on native side (but mirrored for compatibility)
 static const uint32_t sDevKernFlag      = 0x100;
 
@@ -186,6 +187,12 @@
     flags |= -(int)mUnderline    & sUnderlineFlag;
     flags |= -(int)mDevKern      & sDevKernFlag;
     flags |= -(int)mFilterBitmap & sFilterBitmapFlag;
+    if (mRunFlag & minikin::RunFlag::LEFT_EDGE) {
+        flags |= sTextRunLeftEdge;
+    }
+    if (mRunFlag & minikin::RunFlag::RIGHT_EDGE) {
+        flags |= sTextRunRightEdge;
+    }
     return flags;
 }
 
@@ -196,6 +203,15 @@
     mUnderline    = (flags & sUnderlineFlag) != 0;
     mDevKern      = (flags & sDevKernFlag) != 0;
     mFilterBitmap = (flags & sFilterBitmapFlag) != 0;
+
+    std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE;
+    if (flags & sTextRunLeftEdge) {
+        rawFlag |= minikin::RunFlag::LEFT_EDGE;
+    }
+    if (flags & sTextRunRightEdge) {
+        rawFlag |= minikin::RunFlag::RIGHT_EDGE;
+    }
+    mRunFlag = static_cast<minikin::RunFlag>(rawFlag);
 }
 
 }  // namespace android
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8ba7503..d572593 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -613,6 +613,12 @@
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
     const Typeface* typeface = paint->getAndroidTypeface();
     ScopedCharArrayRO text(env, charArray);
+
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     // drawTextString and drawTextChars doesn't use context info
     get_canvas(canvasHandle)->drawText(
             text.get() + index, count,  // text buffer
@@ -620,6 +626,7 @@
             0, count,  // context range
             x, y,  // draw position
             static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+    paint->setRunFlag(originalRunFlag);
 }
 
 static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj,
@@ -629,6 +636,12 @@
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
     const Typeface* typeface = paint->getAndroidTypeface();
     const int count = end - start;
+
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     // drawTextString and drawTextChars doesn't use context info
     get_canvas(canvasHandle)->drawText(
             text.get() + start, count,  // text buffer
@@ -636,6 +649,7 @@
             0, count,  // context range
             x, y,  // draw position
             static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+    paint->setRunFlag(originalRunFlag);
 }
 
 static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray,
@@ -681,9 +695,15 @@
 
     jchar* jchars = env->GetCharArrayElements(text, NULL);
 
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count,
             static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface);
 
+    paint->setRunFlag(originalRunFlag);
     env->ReleaseCharArrayElements(text, jchars, 0);
 }
 
@@ -697,9 +717,15 @@
     const jchar* jchars = env->GetStringChars(text, NULL);
     int count = env->GetStringLength(text);
 
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags),
             *path, hOffset, vOffset, *paint, typeface);
 
+    paint->setRunFlag(originalRunFlag);
     env->ReleaseStringChars(text, jchars);
 }
 
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index c5ffbb7..c8d5987 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -118,7 +118,7 @@
         const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
         const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
         const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
-        const HardwareBufferRenderParams& bufferParams) {
+        const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
     if (!isCapturingSkp() && !mHardwareBuffer) {
         mEglManager.damageFrame(frame, dirty);
     }
@@ -171,6 +171,7 @@
     // Draw visual debugging features
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
                     ProfileType::None != Properties::getProfileType())) {
+        std::scoped_lock lock(profilerLock);
         SkCanvas* profileCanvas = surface->getCanvas();
         SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
         profiler->draw(profileRenderer);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 098a7465..ebe8b6e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -42,7 +42,8 @@
             const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
             const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
             const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
-            const renderthread::HardwareBufferRenderParams& bufferParams) override;
+            const renderthread::HardwareBufferRenderParams& bufferParams,
+            std::mutex& profilerLock) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
     bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
                      const SkRect& screenDirty, FrameInfo* currentFrameInfo,
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index d747489..fd0a8e0 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -75,7 +75,7 @@
         const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
         const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
         const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
-        const HardwareBufferRenderParams& bufferParams) {
+        const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
     sk_sp<SkSurface> backBuffer;
     SkMatrix preTransform;
     if (mHardwareBuffer) {
@@ -103,6 +103,7 @@
     // Draw visual debugging features
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
                     ProfileType::None != Properties::getProfileType())) {
+        std::scoped_lock lock(profilerLock);
         SkCanvas* profileCanvas = backBuffer->getCanvas();
         SkAutoCanvasRestore saver(profileCanvas, true);
         profileCanvas->concat(preTransform);
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index e2ea57d..624eaa5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -42,7 +42,8 @@
             const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
             const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
             const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
-            const renderthread::HardwareBufferRenderParams& bufferParams) override;
+            const renderthread::HardwareBufferRenderParams& bufferParams,
+            std::mutex& profilerLock) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
     bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
                      const SkRect& screenDirty, FrameInfo* currentFrameInfo,
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 56b52dc..9c7f7cc 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -625,14 +625,9 @@
     {
         // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
         // or it can lead to memory corruption.
-        // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
-        // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
-        // the thread we're primarily concerned about being responsive, this being too broad
-        // shouldn't pose a performance issue.
-        std::scoped_lock lock(mFrameMetricsReporterMutex);
-        drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
-                                           &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
-                                           mLightInfo, mRenderNodes, &(profiler()), mBufferParams);
+        drawResult = mRenderPipeline->draw(
+                frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds,
+                mOpaque, mLightInfo, mRenderNodes, &(profiler()), mBufferParams, profilerLock());
     }
 
     uint64_t frameCompleteNr = getFrameNumber();
@@ -762,7 +757,7 @@
             mCurrentFrameInfo->markFrameCompleted();
             mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
                     = mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
-            std::scoped_lock lock(mFrameMetricsReporterMutex);
+            std::scoped_lock lock(mFrameInfoMutex);
             mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter, frameCompleteNr,
                                      mSurfaceControlGenerationId);
         }
@@ -791,7 +786,7 @@
 
 void CanvasContext::reportMetricsWithPresentTime() {
     {  // acquire lock
-        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        std::scoped_lock lock(mFrameInfoMutex);
         if (mFrameMetricsReporter == nullptr) {
             return;
         }
@@ -826,7 +821,7 @@
 
     forthBehind->set(FrameInfoIndex::DisplayPresentTime) = presentTime;
     {  // acquire lock
-        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        std::scoped_lock lock(mFrameInfoMutex);
         if (mFrameMetricsReporter != nullptr) {
             mFrameMetricsReporter->reportFrameMetrics(forthBehind->data(), true /*hasPresentTime*/,
                                                       frameNumber, surfaceControlId);
@@ -835,7 +830,7 @@
 }
 
 void CanvasContext::addFrameMetricsObserver(FrameMetricsObserver* observer) {
-    std::scoped_lock lock(mFrameMetricsReporterMutex);
+    std::scoped_lock lock(mFrameInfoMutex);
     if (mFrameMetricsReporter.get() == nullptr) {
         mFrameMetricsReporter.reset(new FrameMetricsReporter());
     }
@@ -849,7 +844,7 @@
 }
 
 void CanvasContext::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
-    std::scoped_lock lock(mFrameMetricsReporterMutex);
+    std::scoped_lock lock(mFrameInfoMutex);
     if (mFrameMetricsReporter.get() != nullptr) {
         mFrameMetricsReporter->removeObserver(observer);
         if (!mFrameMetricsReporter->hasObservers()) {
@@ -886,7 +881,7 @@
     FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
 
     if (frameInfo != nullptr) {
-        std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
+        std::scoped_lock lock(instance->mFrameInfoMutex);
         frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
                 frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
         frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max(
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index be9b649..e2e3fa3 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -164,6 +164,7 @@
     void notifyFramePending();
 
     FrameInfoVisualizer& profiler() { return mProfiler; }
+    std::mutex& profilerLock() { return mFrameInfoMutex; }
 
     void dumpFrames(int fd);
     void resetFrameStats();
@@ -342,9 +343,8 @@
     std::string mName;
     JankTracker mJankTracker;
     FrameInfoVisualizer mProfiler;
-    std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter
-            GUARDED_BY(mFrameMetricsReporterMutex);
-    std::mutex mFrameMetricsReporterMutex;
+    std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter GUARDED_BY(mFrameInfoMutex);
+    std::mutex mFrameInfoMutex;
 
     std::set<RenderNode*> mPrefetchedLayers;
 
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 9c879d5..b8c3a4d 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -68,7 +68,8 @@
                             const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
                             const std::vector<sp<RenderNode>>& renderNodes,
                             FrameInfoVisualizer* profiler,
-                            const HardwareBufferRenderParams& bufferParams) = 0;
+                            const HardwareBufferRenderParams& bufferParams,
+                            std::mutex& profilerLock) = 0;
     virtual bool swapBuffers(const Frame& frame, IRenderPipeline::DrawResult&,
                              const SkRect& screenDirty, FrameInfo* currentFrameInfo,
                              bool* requireSwap) = 0;
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 9616b5d..17f2525 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -734,7 +734,7 @@
      *     request.
      * @param transferInitiatorPackageName the package name of the app that initiated the transfer.
      *     This value is used with the user handle to populate {@link
-     *     RoutingController#wasTransferRequestedBySelf()}.
+     *     RoutingController#wasTransferInitiatedBySelf()}.
      * @hide
      */
     @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
@@ -1550,11 +1550,11 @@
         }
 
         /**
-         * Returns whether the transfer was requested by the calling app (as determined by comparing
+         * Returns whether the transfer was initiated by the calling app (as determined by comparing
          * {@link UserHandle} and package name).
          */
         @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
-        public boolean wasTransferRequestedBySelf() {
+        public boolean wasTransferInitiatedBySelf() {
             RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
 
             UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
@@ -3084,11 +3084,7 @@
         public void registerRouteCallback() {
             synchronized (mLock) {
                 try {
-                    if (mStub == null) {
-                        MediaRouter2Stub stub = new MediaRouter2Stub();
-                        mMediaRouterService.registerRouter2(stub, mPackageName);
-                        mStub = stub;
-                    }
+                    registerRouterStubIfNeededLocked();
 
                     if (updateDiscoveryPreferenceIfNeededLocked()) {
                         mMediaRouterService.setDiscoveryRequestWithRouter2(
@@ -3114,8 +3110,7 @@
                     }
 
                     if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
-                        mMediaRouterService.unregisterRouter2(mStub);
-                        mStub = null;
+                        unregisterRouterStubLocked();
                     }
                 } catch (RemoteException ex) {
                     Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex);
@@ -3132,11 +3127,7 @@
                 }
                 mRouteListingPreference = preference;
                 try {
-                    if (mStub == null) {
-                        MediaRouter2Stub stub = new MediaRouter2Stub();
-                        mMediaRouterService.registerRouter2(stub, mImpl.getPackageName());
-                        mStub = stub;
-                    }
+                    registerRouterStubIfNeededLocked();
                     mMediaRouterService.setRouteListingPreference(mStub, mRouteListingPreference);
                 } catch (RemoteException ex) {
                     ex.rethrowFromSystemServer();
@@ -3328,18 +3319,31 @@
                             obtainMessage(MediaRouter2::notifyStop, MediaRouter2.this, controller));
                 }
 
-                if (mRouteCallbackRecords.isEmpty()
-                        && mNonSystemRoutingControllers.isEmpty()
-                        && mStub != null) {
+                if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
                     try {
-                        mMediaRouterService.unregisterRouter2(mStub);
+                        unregisterRouterStubLocked();
                     } catch (RemoteException ex) {
                         ex.rethrowFromSystemServer();
                     }
-                    mStub = null;
                 }
             }
         }
 
+        @GuardedBy("mLock")
+        private void registerRouterStubIfNeededLocked() throws RemoteException {
+            if (mStub == null) {
+                MediaRouter2Stub stub = new MediaRouter2Stub();
+                mMediaRouterService.registerRouter2(stub, mPackageName);
+                mStub = stub;
+            }
+        }
+
+        @GuardedBy("mLock")
+        private void unregisterRouterStubLocked() throws RemoteException {
+            if (mStub != null) {
+                mMediaRouterService.unregisterRouter2(mStub);
+                mStub = null;
+            }
+        }
     }
 }
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 7f95886..df9ecdc 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -83,3 +83,10 @@
     description: "Notify ActivityManager with the changes in playback state of the media session."
     bug: "295518668"
 }
+
+flag {
+    name: "enable_prevention_of_keep_alive_route_providers"
+    namespace: "media_solutions"
+    description: "Enables mechanisms to prevent route providers from keeping malicious apps alive."
+    bug: "263520343"
+}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index a8ffd2b..2f6575e 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -149,4 +149,7 @@
     // For CTS purpose only. Add/remove a TvInputHardware device
     void addHardwareDevice(in int deviceId);
     void removeHardwareDevice(in int deviceId);
+
+    // For freezing video playback
+    void setVideoFrozen(in IBinder sessionToken, boolean isFrozen, int userId);
 }
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index e37ee6e..a93f18d 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -83,4 +83,7 @@
     // For TV messages
     void notifyTvMessage(int type, in Bundle data);
     void setTvMessageEnabled(int type, boolean enabled);
+
+    // For freezing video
+    void setVideoFrozen(boolean isFrozen);
 }
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index ae3ee65..921104d 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -81,6 +81,7 @@
     private static final int DO_NOTIFY_TV_MESSAGE = 32;
     private static final int DO_STOP_PLAYBACK = 33;
     private static final int DO_START_PLAYBACK = 34;
+    private static final int DO_SET_VIDEO_FROZEN = 35;
 
     private final boolean mIsRecordingSession;
     private final HandlerCaller mCaller;
@@ -296,6 +297,10 @@
                 mTvInputSessionImpl.startPlayback();
                 break;
             }
+            case DO_SET_VIDEO_FROZEN: {
+                mTvInputSessionImpl.setVideoFrozen((Boolean) msg.obj);
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -483,6 +488,11 @@
     }
 
     @Override
+    public void setVideoFrozen(boolean isFrozen) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VIDEO_FROZEN, isFrozen));
+    }
+
+    @Override
     public void notifyTvMessage(int type, Bundle data) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
     }
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index c685a5a..51b2542 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -3334,6 +3334,18 @@
             }
         }
 
+        void setVideoFrozen(boolean isFrozen) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.setVideoFrozen(mToken, isFrozen, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         /**
          * Sends TV messages to the service for testing purposes
          */
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 55fa517..76d8e50 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1558,6 +1558,17 @@
         }
 
         /**
+         * Called when a request to freeze the video is received from the TV app. The audio should
+         * continue playback while the video is frozen.
+         *
+         * <p> This should freeze the video to the last frame when the state is set to {@code true}.
+         * @param isFrozen whether or not the video should be frozen.
+         * @hide
+         */
+        public void onSetVideoFrozen(boolean isFrozen) {
+        }
+
+        /**
          * Called when the application requests to play a given recorded TV program.
          *
          * @param recordedProgramUri The URI of a recorded TV program.
@@ -2034,6 +2045,13 @@
         }
 
         /**
+         * Calls {@link #onSetVideoFrozen(boolean)}.
+         */
+        void setVideoFrozen(boolean isFrozen) {
+            onSetVideoFrozen(isFrozen);
+        }
+
+        /**
          * Calls {@link #onTimeShiftPlay(Uri)}.
          */
         void timeShiftPlay(Uri recordedProgramUri) {
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 233f966..cb45661 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -675,6 +675,23 @@
     }
 
     /**
+     * Sets whether or not the video is frozen. While the video is frozen, audio playback will
+     * continue.
+     *
+     * <p> This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
+     * received with the command to freeze the video.
+     *
+     * <p> This will freeze the video to the last frame when the state is set to {@code true}.
+     * @param isFrozen whether or not the video is frozen.
+     * @hide
+     */
+    public void setVideoFrozen(boolean isFrozen) {
+        if (mSession != null) {
+            mSession.setVideoFrozen(isFrozen);
+        }
+    }
+
+    /**
      * Sends TV messages to the session for testing purposes
      *
      * @hide
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 5cc86ba..7936403 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -112,7 +112,8 @@
             PLAYBACK_COMMAND_TYPE_TUNE_PREV,
             PLAYBACK_COMMAND_TYPE_STOP,
             PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME,
-            PLAYBACK_COMMAND_TYPE_SELECT_TRACK
+            PLAYBACK_COMMAND_TYPE_SELECT_TRACK,
+            PLAYBACK_COMMAND_TYPE_FREEZE
     })
     public @interface PlaybackCommandType {}
 
@@ -142,8 +143,11 @@
      * Playback command type: select the given track.
      */
     public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
-
-
+    /**
+     * Playback command type: freeze the video playback on the current frame.
+     * @hide
+     */
+    public static final String PLAYBACK_COMMAND_TYPE_FREEZE = "freeze";
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index f9eaabd..0b9429d 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -264,6 +264,25 @@
         }
     }
 
+    /* package */ void closeInternal() {
+        synchronized (mLock) {
+            if (mIsClosed) {
+                return;
+            }
+            int res = nativeClose();
+            if (res != Tuner.RESULT_SUCCESS) {
+                TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
+            } else {
+                mIsClosed = true;
+                if (mOwner != null) {
+                    mOwner.releaseLnb();
+                    mOwner = null;
+                }
+                mCallbackMap.clear();
+            }
+        }
+    }
+
     /**
      * Sets the LNB's power voltage.
      *
@@ -330,22 +349,7 @@
     public void close() {
         acquireTRMSLock("close()");
         try {
-            synchronized (mLock) {
-                if (mIsClosed) {
-                    return;
-                }
-                int res = nativeClose();
-                if (res != Tuner.RESULT_SUCCESS) {
-                    TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
-                } else {
-                    mIsClosed = true;
-                    if (mOwner != null) {
-                        mOwner.releaseLnb();
-                        mOwner = null;
-                    }
-                    mCallbackMap.clear();
-                }
-            }
+            closeInternal();
         } finally {
             releaseTRMSLock();
         }
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 09f09b9..f28c2c1 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -30,6 +30,7 @@
 import android.hardware.tv.tuner.Constant;
 import android.hardware.tv.tuner.Constant64Bit;
 import android.hardware.tv.tuner.FrontendScanType;
+import android.media.MediaCodec;
 import android.media.tv.TvInputService;
 import android.media.tv.tuner.dvr.DvrPlayback;
 import android.media.tv.tuner.dvr.DvrRecorder;
@@ -272,8 +273,12 @@
         try {
             System.loadLibrary("media_tv_tuner");
             nativeInit();
+            // Load and initialize MediaCodec to avoid flaky cts test result.
+            Class.forName(MediaCodec.class.getName());
         } catch (UnsatisfiedLinkError e) {
             Log.d(TAG, "tuner JNI library not found!");
+        } catch (ClassNotFoundException e) {
+            Log.e(TAG, "MediaCodec class not found!", e);
         }
     }
 
@@ -914,7 +919,7 @@
                 if (DEBUG) {
                     Log.d(TAG, "calling mLnb.close() : " + mClientId);
                 }
-                mLnb.close();
+                mLnb.closeInternal();
             } else {
                 if (DEBUG) {
                     Log.d(TAG, "NOT calling mLnb.close() : " + mClientId);
@@ -2348,6 +2353,7 @@
     @Nullable
     public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
             @NonNull LnbCallback cb) {
+        acquireTRMSLock("openLnbByName");
         mLnbLock.lock();
         try {
             Objects.requireNonNull(name, "LNB name must not be null");
@@ -2356,7 +2362,7 @@
             Lnb newLnb = nativeOpenLnbByName(name);
             if (newLnb != null) {
                 if (mLnb != null) {
-                    mLnb.close();
+                    mLnb.closeInternal();
                     mLnbHandle = null;
                 }
                 mLnb = newLnb;
@@ -2367,6 +2373,7 @@
             }
             return mLnb;
         } finally {
+            releaseTRMSLock();
             mLnbLock.unlock();
         }
     }
@@ -2784,8 +2791,8 @@
         }
     }
 
+    // Must be called while TRMS lock is being held
     /* package */ void releaseLnb() {
-        acquireTRMSLock("releaseLnb()");
         mLnbLock.lock();
         try {
             if (mLnbHandle != null) {
@@ -2802,7 +2809,6 @@
             }
             mLnb = null;
         } finally {
-            releaseTRMSLock();
             mLnbLock.unlock();
         }
     }
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 74bec3e..7136866 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -68,7 +68,6 @@
     ],
     jarjar_rules: ":nfc-jarjar-rules",
     lint: {
-        strict_updatability_linting: true,
         baseline_filename: "lint-baseline.xml",
     },
     apex_available: [
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 24c145f..7573474 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -204,6 +204,7 @@
     method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
     method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String);
     method public boolean removeAidsForService(android.content.ComponentName, String);
     method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
     method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 286cf28..bec62c5 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -97,4 +97,7 @@
     WlcLDeviceInfo getWlcLDeviceInfo();
 
     void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
+
+    void notifyPollingLoop(in Bundle frame);
+    void notifyHceDeactivated();
 }
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index f4b4604..791bd8c 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -32,6 +32,7 @@
     boolean setDefaultForNextTap(int userHandle, in ComponentName service);
     boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable);
     boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
+    boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter);
     boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
     boolean unsetOffHostForService(int userHandle, in ComponentName service);
     AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 8219d2f..68c16e6 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -26,6 +26,7 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -2752,6 +2753,64 @@
         }
     }
 
+   /**
+     * Notifies the system of a new polling loop.
+     *
+     * @param frame is the new frame.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void notifyPollingLoop(@NonNull Bundle frame) {
+        try {
+            if (sService == null) {
+                attemptDeadServiceRecovery(null);
+            }
+            sService.notifyPollingLoop(frame);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return;
+            }
+            try {
+                sService.notifyPollingLoop(frame);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+        }
+    }
+
+   /**
+     * Notifies the system of a an HCE session being deactivated.
+     *     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void notifyHceDeactivated() {
+        try {
+            if (sService == null) {
+                attemptDeadServiceRecovery(null);
+            }
+            sService.notifyHceDeactivated();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return;
+            }
+            try {
+                sService.notifyHceDeactivated();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+        }
+    }
+
     /**
      * Sets NFC charging feature.
      * <p>This API is for the Settings application.
@@ -2767,6 +2826,7 @@
         }
         try {
             return sService.enableWlc(enable);
+
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             // Try one more time
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 41dee3a..426c5aa 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,6 +52,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.regex.Pattern;
 
@@ -102,6 +103,8 @@
      */
     private final HashMap<String, AidGroup> mDynamicAidGroups;
 
+    private final ArrayList<String> mPollingLoopFilters;
+
     /**
      * Whether this service should only be started when the device is unlocked.
      */
@@ -169,6 +172,7 @@
         this.mDescription = description;
         this.mStaticAidGroups = new HashMap<String, AidGroup>();
         this.mDynamicAidGroups = new HashMap<String, AidGroup>();
+        this.mPollingLoopFilters = new ArrayList<String>();
         this.mOffHostName = offHost;
         this.mStaticOffHostName = staticOffHost;
         this.mOnHost = onHost;
@@ -282,6 +286,7 @@
 
             mStaticAidGroups = new HashMap<String, AidGroup>();
             mDynamicAidGroups = new HashMap<String, AidGroup>();
+            mPollingLoopFilters = new ArrayList<String>();
             mOnHost = onHost;
 
             final int depth = parser.getDepth();
@@ -364,6 +369,15 @@
                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
                     }
                     a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG
+                        && "polling-loop-filter".equals(tagName) && currentGroup == null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.PollingLoopFilter);
+                    String plf =
+                            a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
+                            .toUpperCase(Locale.ROOT);
+                    mPollingLoopFilters.add(plf);
+                    a.recycle();
                 }
             }
         } catch (NameNotFoundException e) {
@@ -420,6 +434,16 @@
     }
 
     /**
+     * Returns the current polling loop filters for this service.
+     * @return List of polling loop filters.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    @NonNull
+    public List<String> getPollingLoopFilters() {
+        return mPollingLoopFilters;
+    }
+
+    /**
      * Returns a consolidated list of AIDs with prefixes from the AID groups
      * registered by this service. Note that if a service has both
      * a static (manifest-based) AID group for a category and a dynamic
@@ -596,6 +620,26 @@
     }
 
     /**
+     * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be
+     * delivered to {@link HostApduService#processPollingFrames(List)}.
+     * @param pollingLoopFilter this polling loop filter to add.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void addPollingLoopFilter(@NonNull String pollingLoopFilter) {
+        mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+    }
+
+    /**
+     * Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
+     * longer be delivered to {@link HostApduService#processPollingFrames(List)}.
+     * @param pollingLoopFilter this polling loop filter to add.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void removePollingLoopFilter(@NonNull String pollingLoopFilter) {
+        mPollingLoopFilters.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
+    }
+
+    /**
      * Sets the off host Secure Element.
      * @param  offHost  Secure Element to set. Only accept strings with prefix SIM or prefix eSE.
      *                  Ref: GSMA TS.26 - NFC Handset Requirements
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 81eab71..0943392 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -57,6 +57,8 @@
  */
 public final class CardEmulation {
     private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+    private static final Pattern PLF_PATTERN = Pattern.compile("[0-9A-Fa-f]{1,32}");
+
     static final String TAG = "CardEmulation";
 
     /**
@@ -353,6 +355,34 @@
     }
 
     /**
+     * Register a polling loop filter for a HostApduService.
+     * @param service The HostApduService to register the filter for.
+     * @param pollingLoopFilter The filter to register.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public boolean registerPollingLoopFilterForService(@NonNull ComponentName service,
+            @NonNull String pollingLoopFilter) {
+        try {
+            return sService.registerPollingLoopFilterForService(mContext.getUser().getIdentifier(),
+                    service, pollingLoopFilter);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.registerPollingLoopFilterForService(
+                        mContext.getUser().getIdentifier(), service, pollingLoopFilter);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
      * Registers a list of AIDs for a specific category for the
      * specified service.
      *
@@ -933,6 +963,24 @@
     }
 
     /**
+     * Tests the validity of the polling loop filter.
+     * @param pollingLoopFilter The polling loop filter to test.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static boolean isValidPollingLoopFilter(@NonNull String pollingLoopFilter) {
+        // Verify hex characters
+        if (!PLF_PATTERN.matcher(pollingLoopFilter).matches()) {
+            Log.e(TAG, "Polling Loop Filter " + pollingLoopFilter
+                    + " is not a valid Polling Loop Filter.");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
      * A valid AID according to ISO/IEC 7816-4:
      * <ul>
      * <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 2da8c8c..221ca4f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.os.Flags;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
@@ -131,7 +132,7 @@
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
         final boolean isArchive =
-                android.content.pm.Flags.archiving() && (
+                isArchivingEnabled() && (
                         (dialogInfo.deleteFlags & PackageManager.DELETE_ARCHIVE) != 0);
         final UserHandle myUserHandle = Process.myUserHandle();
         UserManager userManager = getContext().getSystemService(UserManager.class);
@@ -242,6 +243,11 @@
         return dialogBuilder.create();
     }
 
+    private static boolean isArchivingEnabled() {
+        return android.content.pm.Flags.archiving()
+                || SystemProperties.getBoolean("pm.archiving.enabled", false);
+    }
+
     private boolean isCloneProfile(UserHandle userHandle) {
         UserManager customUserManager = getContext()
                 .createContextAsUser(UserHandle.of(userHandle.getIdentifier()), 0)
diff --git a/packages/SettingsLib/ActionBarShadow/Android.bp b/packages/SettingsLib/ActionBarShadow/Android.bp
index 6f94458..77cbb00 100644
--- a/packages/SettingsLib/ActionBarShadow/Android.bp
+++ b/packages/SettingsLib/ActionBarShadow/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibActionBarShadow",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp
index 1228555..c36b82d 100644
--- a/packages/SettingsLib/ActionButtonsPreference/Android.bp
+++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibActionButtonsPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp
index 41de29a..838a9e5 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibActivityEmbedding",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index 044ba87..67b6fb5 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibAdaptiveIcon",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/AppPreference/Android.bp b/packages/SettingsLib/AppPreference/Android.bp
index 69b9d44..c5b2ef6 100644
--- a/packages/SettingsLib/AppPreference/Android.bp
+++ b/packages/SettingsLib/AppPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibAppPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index da91344..07290de 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibBannerMessagePreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/BarChartPreference/Android.bp b/packages/SettingsLib/BarChartPreference/Android.bp
index be1e0cf..448ed56 100644
--- a/packages/SettingsLib/BarChartPreference/Android.bp
+++ b/packages/SettingsLib/BarChartPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibBarChartPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index 35572fad..0382829 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibButtonPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 70f7554..87ec0b8 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibCollapsingToolbarBaseActivity",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/DisplayUtils/Android.bp b/packages/SettingsLib/DisplayUtils/Android.bp
index eab35a1..279bb70 100644
--- a/packages/SettingsLib/DisplayUtils/Android.bp
+++ b/packages/SettingsLib/DisplayUtils/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibDisplayUtils",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
index 17b662c..83f81c6 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/Android.bp
+++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
@@ -10,13 +10,16 @@
 android_library {
     name: "SettingsLibEntityHeaderWidgets",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
 
     static_libs: [
-          "androidx.annotation_annotation",
-          "SettingsLibSettingsTheme"
+        "androidx.annotation_annotation",
+        "SettingsLibSettingsTheme",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp
index b45cd65..d1ad80d 100644
--- a/packages/SettingsLib/FooterPreference/Android.bp
+++ b/packages/SettingsLib/FooterPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibFooterPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/HelpUtils/Android.bp b/packages/SettingsLib/HelpUtils/Android.bp
index 041fce2..284106e 100644
--- a/packages/SettingsLib/HelpUtils/Android.bp
+++ b/packages/SettingsLib/HelpUtils/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibHelpUtils",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp
index 4d4759b..6407810 100644
--- a/packages/SettingsLib/IllustrationPreference/Android.bp
+++ b/packages/SettingsLib/IllustrationPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibIllustrationPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/LayoutPreference/Android.bp b/packages/SettingsLib/LayoutPreference/Android.bp
index 53ded23..8cf636a 100644
--- a/packages/SettingsLib/LayoutPreference/Android.bp
+++ b/packages/SettingsLib/LayoutPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibLayoutPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 010a6ce..b984aaf 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibMainSwitchPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ProfileSelector/Android.bp b/packages/SettingsLib/ProfileSelector/Android.bp
index 155ed2e..6dc07b2 100644
--- a/packages/SettingsLib/ProfileSelector/Android.bp
+++ b/packages/SettingsLib/ProfileSelector/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibProfileSelector",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index 3b04bd9..8d722eb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -16,6 +16,9 @@
 android_library {
     name: "SettingsLibRestrictedLockUtils",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
index 22e4e94..c0fc741 100644
--- a/packages/SettingsLib/SchedulesProvider/Android.bp
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibSchedulesProvider",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp
index c385d38..61ed65c 100644
--- a/packages/SettingsLib/SearchProvider/Android.bp
+++ b/packages/SettingsLib/SearchProvider/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibSearchProvider",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
index 702387e..2fe446d 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibSelectorWithWidgetPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SettingsSpinner/Android.bp b/packages/SettingsLib/SettingsSpinner/Android.bp
index 0eec505..8fed61f 100644
--- a/packages/SettingsLib/SettingsSpinner/Android.bp
+++ b/packages/SettingsLib/SettingsSpinner/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibSettingsSpinner",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
index 06493c0..e04af6c 100644
--- a/packages/SettingsLib/SettingsTransition/Android.bp
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibSettingsTransition",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 8b136da..6f9556f 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.6.0-beta02"
+    extra["jetpackComposeVersion"] = "1.6.0-rc01"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index ed90284..df5644b 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -74,8 +74,7 @@
             android:exported="false">
         </provider>
         <activity
-            android:name="com.android.settingslib.spa.gallery.SpaDialogActivity"
-            android:excludeFromRecents="true"
+            android:name="com.android.settingslib.spa.gallery.GalleryDialogActivity"
             android:exported="true"
             android:theme="@style/Theme.SpaLib.Dialog">
         </activity>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt
new file mode 100644
index 0000000..e22ed35
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.settingslib.spa.gallery
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import com.android.settingslib.spa.SpaBaseDialogActivity
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
+import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon
+
+class GalleryDialogActivity : SpaBaseDialogActivity() {
+    @Composable
+    override fun Content() {
+        SettingsAlertDialogWithIcon(
+            onDismissRequest = { finish() },
+            confirmButton = AlertDialogButton("confirm") { finish() },
+            dismissButton = AlertDialogButton("dismiss") { finish() },
+            title = "title",
+            text = {
+                Text(
+                    "text",
+                    modifier = Modifier.fillMaxWidth(),
+                    textAlign = TextAlign.Center
+                )
+            }
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt
deleted file mode 100644
index 8b80fe2..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.settingslib.spa.gallery
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.WarningAmber
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Button
-import androidx.compose.material3.Icon
-import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import com.android.settingslib.spa.framework.common.LogCategory
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.dialog.getDialogWidth
-
-
-class SpaDialogActivity : ComponentActivity() {
-    private val spaEnvironment get() = SpaEnvironmentFactory.instance
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
-        setContent {
-            SettingsTheme {
-                Content()
-            }
-        }
-    }
-
-    @Composable
-    fun Content() {
-        var openAlertDialog by remember { mutableStateOf(false) }
-        AlertDialog(openAlertDialog)
-        LaunchedEffect(key1 = Unit) {
-            openAlertDialog = true
-        }
-    }
-
-    @Composable
-    fun AlertDialog(openAlertDialog: Boolean) {
-        when {
-            openAlertDialog -> {
-                AlertDialogExample(
-                    onDismissRequest = { finish() },
-                    onConfirmation = { finish() },
-                    dialogTitle = intent.getStringExtra(DIALOG_TITLE) ?: DIALOG_TITLE,
-                    dialogText = intent.getStringExtra(DIALOG_TEXT) ?: DIALOG_TEXT,
-                    icon = Icons.Default.WarningAmber
-                )
-            }
-        }
-    }
-
-    @Composable
-    fun AlertDialogExample(
-        onDismissRequest: () -> Unit,
-        onConfirmation: () -> Unit,
-        dialogTitle: String,
-        dialogText: String,
-        icon: ImageVector,
-    ) {
-        AlertDialog(
-            modifier = Modifier.width(getDialogWidth()),
-            icon = {
-                Icon(icon, contentDescription = null)
-            },
-            title = {
-                Text(text = dialogTitle)
-            },
-            text = {
-                Text(text = dialogText)
-            },
-            onDismissRequest = {
-                onDismissRequest()
-            },
-            dismissButton = {
-                OutlinedButton(
-                    onClick = {
-                        onDismissRequest()
-                    }
-                ) {
-                    Text(intent.getStringExtra(DISMISS_TEXT) ?: DISMISS_TEXT)
-                }
-            },
-            confirmButton = {
-                Button(
-                    onClick = {
-                        onConfirmation()
-                    },
-                ) {
-                    Text(intent.getStringExtra(CONFIRM_TEXT) ?: CONFIRM_TEXT)
-                }
-            }
-        )
-    }
-
-    companion object {
-        private const val TAG = "SpaDialogActivity"
-        private const val DIALOG_TITLE = "dialogTitle"
-        private const val DIALOG_TEXT = "dialogText"
-        private const val CONFIRM_TEXT = "confirmText"
-        private const val DISMISS_TEXT = "dismissText"
-    }
-}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 9703c347..1f78a9c 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.2.0"
+agp = "8.2.1"
 compose-compiler = "1.5.1"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 7eccfe5..618dc37 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
     api("androidx.slice:slice-builders:1.1.0-alpha02")
     api("androidx.slice:slice-core:1.1.0-alpha02")
     api("androidx.slice:slice-view:1.1.0-alpha02")
-    api("androidx.compose.material3:material3:1.2.0-alpha12")
+    api("androidx.compose.material3:material3:1.2.0-beta02")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.7.4")
+    api("androidx.navigation:navigation-compose:2.7.6")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.7.0-alpha03")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt
new file mode 100644
index 0000000..dfb780a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.settingslib.spa
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+abstract class SpaBaseDialogActivity : ComponentActivity() {
+    private val spaEnvironment get() = SpaEnvironmentFactory.instance
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
+        setContent {
+            SettingsTheme {
+                Content()
+            }
+        }
+    }
+
+    @Composable
+    abstract fun Content()
+
+    companion object {
+        private const val TAG = "SpaBaseDialogActivity"
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 5605485..da1ee77 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -22,12 +22,14 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
 import androidx.core.view.WindowCompat
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavGraph.Companion.findStartDestination
@@ -133,6 +135,7 @@
     NavHost(
         navController = navController,
         startDestination = NullPageProvider.name,
+        modifier = Modifier.fillMaxSize(),
     ) {
         composable(NullPageProvider.name) {}
         for (spp in allProvider) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 81bee5e..0281ab8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -86,7 +86,7 @@
     )
 }
 
-object NullPageProvider : SettingsPageProvider {
+internal object NullPageProvider : SettingsPageProvider {
     override val name = NULL_PAGE_NAME
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
index 192b125..93ad644 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
@@ -30,6 +30,7 @@
 import androidx.navigation.NavDeepLink
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.compose.composable
+import com.android.settingslib.spa.framework.common.NullPageProvider
 
 /**
  * Add the [Composable] to the [NavGraphBuilder] with animation
@@ -49,11 +50,13 @@
     arguments = arguments,
     deepLinks = deepLinks,
     enterTransition = {
-        slideIntoContainer(
-            towards = AnimatedContentTransitionScope.SlideDirection.Start,
-            animationSpec = slideInEffect,
-            initialOffset = offsetFunc,
-        ) + fadeIn(animationSpec = fadeInEffect)
+        if (initialState.destination.route != NullPageProvider.name) {
+            slideIntoContainer(
+                towards = AnimatedContentTransitionScope.SlideDirection.Start,
+                animationSpec = slideInEffect,
+                initialOffset = offsetFunc,
+            ) + fadeIn(animationSpec = fadeInEffect)
+        } else null
     },
     exitTransition = {
         slideOutOfContainer(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index 8ffd799..de080e3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -102,8 +102,8 @@
 fun getDialogWidth(): Dp {
     val configuration = LocalConfiguration.current
     return configuration.screenWidthDp.dp * when (configuration.orientation) {
-        Configuration.ORIENTATION_LANDSCAPE -> 0.6f
-        else -> 0.8f
+        Configuration.ORIENTATION_LANDSCAPE -> 0.65f
+        else -> 0.85f
     }
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
new file mode 100644
index 0000000..1695e4f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.settingslib.spa.widget.dialog
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.WarningAmber
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.window.DialogProperties
+
+@Composable
+fun SettingsAlertDialogWithIcon(
+    onDismissRequest: () -> Unit,
+    confirmButton: AlertDialogButton?,
+    dismissButton: AlertDialogButton?,
+    title: String?,
+    text: @Composable (() -> Unit)?,
+) {
+    AlertDialog(
+        onDismissRequest = onDismissRequest,
+        icon = { Icon(Icons.Default.WarningAmber, contentDescription = null) },
+        modifier = Modifier.width(getDialogWidth()),
+        confirmButton = {
+            confirmButton?.let {
+                Button(
+                    onClick = {
+                        it.onClick()
+                    },
+                ) {
+                    Text(it.text)
+                }
+            }
+        },
+        dismissButton = dismissButton?.let {
+            {
+                OutlinedButton(
+                    onClick = {
+                        it.onClick()
+                    },
+                ) {
+                    Text(it.text)
+                }
+            }
+        },
+        title = title?.let {
+            {
+                Text(
+                    it,
+                    modifier = Modifier.fillMaxWidth(),
+                    textAlign = TextAlign.Center
+                )
+            }
+        },
+        text = text?.let {
+            {
+                Column(Modifier.verticalScroll(rememberScrollState())) {
+                    text()
+                }
+            }
+        },
+        properties = DialogProperties(usePlatformDefaultWidth = false),
+    )
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index abeffec..0a98791 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.ResolveInfo
+import android.os.SystemProperties
 import com.android.internal.R
 import com.android.settingslib.spaprivileged.framework.common.userManager
 import kotlinx.coroutines.async
@@ -110,7 +111,7 @@
     ): List<ApplicationInfo> {
         val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or
             PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
-        val archivedPackagesFlag: Long = if (featureFlags.archiving())
+        val archivedPackagesFlag: Long = if (isArchivingEnabled(featureFlags))
             PackageManager.MATCH_ARCHIVED_PACKAGES else 0L
         val regularFlags = ApplicationInfoFlags.of(
             disabledComponentsFlag or
@@ -148,6 +149,9 @@
         }
     }
 
+    private fun isArchivingEnabled(featureFlags: FeatureFlags) =
+            featureFlags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false)
+
     override fun showSystemPredicate(
         userIdFlow: Flow<Int>,
         showSystemFlow: Flow<Boolean>,
diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp
index 19c59dd2..54b9748 100644
--- a/packages/SettingsLib/Tile/Android.bp
+++ b/packages/SettingsLib/Tile/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibTile",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp
index 77b7ac1..e70201b 100644
--- a/packages/SettingsLib/TopIntroPreference/Android.bp
+++ b/packages/SettingsLib/TopIntroPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibTopIntroPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 5aa906e..70d9630 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibTwoTargetPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index 4cc90cc..0a83aab 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibUsageProgressBarPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index d5a56c8..5d2aef7 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibUtils",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 390c9d2e..1f8d1dd 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -17,6 +17,9 @@
 android_library {
     name: "SettingsLib-search",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
     static_libs: [
         "SettingsLib-search-interface",
     ],
diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING
index 890510f..0eed2b7 100644
--- a/packages/SettingsProvider/TEST_MAPPING
+++ b/packages/SettingsProvider/TEST_MAPPING
@@ -11,5 +11,10 @@
                 }
             ]
         }
+    ],
+    "postsubmit": [
+        {
+            "name": "CtsDeviceConfigTestCases"
+        }
     ]
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5004f25..8ae50eb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -241,6 +241,8 @@
         Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
         Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
         Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+        Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+        Settings.Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS,
         Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
         Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER,
         Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 0b0e182..285c8c9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -387,6 +387,11 @@
         VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_CODE, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(
+                Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+                new DiscreteValueValidator(new String[] {"0", "1"}));
+        VALIDATORS.put(
+                Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index f204e48..db1ca95 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -559,6 +559,9 @@
     <!-- Permission required for CTS test - android.server.biometrics -->
     <uses-permission android:name="android.permission.TEST_BIOMETRIC" />
 
+    <!-- Permission required for CTS test - android.server.biometrics -->
+    <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
+
     <!-- Permissions required for CTS test - NotificationManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
 
@@ -748,6 +751,7 @@
     <uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
     <uses-permission android:name="android.permission.MODIFY_CELL_BROADCASTS" />
     <uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
+    <uses-permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID" />
 
     <!-- Permission required for CTS test - CtsPersistentDataBlockManagerTestCases -->
     <uses-permission android:name="android.permission.ACCESS_PDB_STATE" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index b464498..3236130 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -15,6 +15,13 @@
 }
 
 flag {
+    name: "notification_async_group_header_inflation"
+    namespace: "systemui"
+    description: "Inflates the notification group summary header views from the background thread."
+    bug: "217799515"
+}
+
+flag {
     name: "notification_async_hybrid_view_inflation"
     namespace: "systemui"
     description: "Inflates hybrid (single-line) notification views from the background thread."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index b704864..c073b79b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -67,8 +67,9 @@
     // Don't show hub mode UI if keyguard is not present. This is important since we're in the
     // shade, which can be opened from many locations.
     val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
+    val isCommunalAvailable by viewModel.isCommunalAvailable.collectAsState()
 
-    if (!isKeyguardShowing) {
+    if (!isKeyguardShowing || !isCommunalAvailable) {
         return
     }
 
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 c8e18d7..a390305 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
@@ -69,7 +69,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
@@ -90,9 +89,11 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.compose.ui.window.Popup
+import androidx.core.view.setPadding
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
 import com.android.systemui.communal.ui.compose.extensions.allowGestures
 import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
 import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
@@ -474,7 +475,7 @@
     Card(
         modifier = modifier.size(Dp(size.width), Dp(size.height)),
         colors = CardDefaults.cardColors(containerColor = Color.Transparent),
-        border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed),
+        border = BorderStroke(CardOutlineWidth, LocalAndroidColorScheme.current.tertiaryFixed),
         shape = RoundedCornerShape(16.dp)
     ) {}
 }
@@ -488,7 +489,7 @@
 ) {
     val colors = LocalAndroidColorScheme.current
     Card(
-        modifier = modifier.height(size.height.dp),
+        modifier = modifier.height(size.height.dp).padding(CardOutlineWidth),
         colors =
             CardDefaults.cardColors(
                 containerColor = colors.primary,
@@ -560,7 +561,7 @@
     }
     val colors = LocalAndroidColorScheme.current
     Card(
-        modifier = modifier.height(size.height.dp),
+        modifier = modifier.height(size.height.dp).padding(CardOutlineWidth),
         colors = CardDefaults.cardColors(containerColor = Color.Transparent),
         border = BorderStroke(1.dp, colors.primary),
         shape = RoundedCornerShape(200.dp),
@@ -599,6 +600,7 @@
         modifier = modifier.height(size.height.dp),
         contentAlignment = Alignment.Center,
     ) {
+        val paddingInPx = with(LocalDensity.current) { CardOutlineWidth.toPx().toInt() }
         AndroidView(
             modifier = modifier.allowGestures(allowed = !viewModel.isEditMode),
             factory = { context ->
@@ -610,9 +612,13 @@
                 model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler())
                 val view =
                     model.appWidgetHost
-                        .createView(context, model.appWidgetId, model.providerInfo)
+                        .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
                         .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
                 model.appWidgetHost.setInteractionHandler(null)
+                // Remove the extra padding applied to AppWidgetHostView to allow widgets to
+                // occupy the entire box. The added padding is now adjusted to leave only sufficient
+                // space for displaying the outline around the box when the widget is selected.
+                view.setPadding(paddingInPx)
                 view
             },
             // For reusing composition in lazy lists.
@@ -726,6 +732,7 @@
     val CardHeightFull = 630.dp
     val CardHeightHalf = 307.dp
     val CardHeightThird = 199.dp
+    val CardOutlineWidth = 3.dp
     val GridHeight = CardHeightFull
     val Spacing = 16.dp
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 085dcf4..1545372 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -42,7 +42,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.controls.ui.MediaCarouselController
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
@@ -126,6 +128,12 @@
             modifier = modifier,
         )
 
+    init {
+        mediaHost.expansion = MediaHostState.EXPANDED
+        mediaHost.showsOnlyActiveMedia = true
+        mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+    }
+
     private fun destinationScenes(
         up: SceneKey,
     ): Map<UserAction, SceneModel> {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index 2c96d0e..8e35988 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -36,24 +36,25 @@
 fun LargeTopAppBarNestedScrollConnection(
     height: () -> Float,
     onHeightChanged: (Float) -> Unit,
-    heightRange: ClosedFloatingPointRange<Float>,
+    minHeight: () -> Float,
+    maxHeight: () -> Float,
 ): PriorityNestedScrollConnection {
-    val minHeight = heightRange.start
-    val maxHeight = heightRange.endInclusive
     return PriorityNestedScrollConnection(
         orientation = Orientation.Vertical,
         // When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will
         // expand. Then, you can then scroll down the content.
         canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
-            offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight
+            offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight()
         },
         // When swiping down, the content will scroll up until it reaches the top. Then, the
         // LargeTopAppBar will expand until it reaches its [maxHeight].
-        canStartPostScroll = { offsetAvailable, _ -> offsetAvailable > 0 && height() < maxHeight },
+        canStartPostScroll = { offsetAvailable, _ ->
+            offsetAvailable > 0 && height() < maxHeight()
+        },
         canStartPostFling = { false },
         canContinueScroll = {
             val currentHeight = height()
-            minHeight < currentHeight && currentHeight < maxHeight
+            minHeight() < currentHeight && currentHeight < maxHeight()
         },
         canScrollOnFling = true,
         onStart = { /* do nothing */},
@@ -61,10 +62,10 @@
             val currentHeight = height()
             val amountConsumed =
                 if (offsetAvailable > 0) {
-                    val amountLeft = maxHeight - currentHeight
+                    val amountLeft = maxHeight() - currentHeight
                     offsetAvailable.coerceAtMost(amountLeft)
                 } else {
-                    val amountLeft = minHeight - currentHeight
+                    val amountLeft = minHeight() - currentHeight
                     offsetAvailable.coerceAtLeast(amountLeft)
                 }
             onHeightChanged(currentHeight + amountConsumed)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index e2974cd..ac7717b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -34,7 +34,8 @@
         LargeTopAppBarNestedScrollConnection(
             height = { height },
             onHeightChanged = { height = it },
-            heightRange = heightRange,
+            minHeight = { heightRange.start },
+            maxHeight = { heightRange.endInclusive },
         )
 
     private fun NestedScrollConnection.scroll(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index bc3ca1b..2a793ea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -47,16 +47,20 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -66,6 +70,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.util.mockito.any
@@ -156,7 +161,7 @@
     private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
     private lateinit var keyguardPasswordView: KeyguardPasswordView
     private lateinit var testableResources: TestableResources
-    private lateinit var sceneTestUtils: SceneTestUtils
+    private lateinit var kosmos: Kosmos
     private lateinit var sceneInteractor: SceneInteractor
     private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var deviceEntryInteractor: DeviceEntryInteractor
@@ -222,15 +227,15 @@
                 mSelectedUserInteractor,
             )
 
-        sceneTestUtils = SceneTestUtils(this)
-        sceneInteractor = sceneTestUtils.sceneInteractor()
+        kosmos = testKosmos()
+        sceneInteractor = kosmos.sceneInteractor
         keyguardTransitionInteractor =
-            KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(kosmos.testScope.backgroundScope)
                 .keyguardTransitionInteractor
         sceneTransitionStateFlow =
             MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
         sceneInteractor.setTransitionState(sceneTransitionStateFlow)
-        deviceEntryInteractor = sceneTestUtils.deviceEntryInteractor()
+        deviceEntryInteractor = kosmos.deviceEntryInteractor
 
         mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         underTest =
@@ -249,7 +254,7 @@
                 falsingManager,
                 userSwitcherController,
                 featureFlags,
-                sceneTestUtils.fakeSceneContainerFlags,
+                kosmos.fakeSceneContainerFlags,
                 globalSettings,
                 sessionTracker,
                 Optional.of(sideFpsController),
@@ -259,7 +264,7 @@
                 audioManager,
                 faceAuthInteractor,
                 mock(),
-                { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
+                { JavaAdapter(kosmos.testScope.backgroundScope) },
                 mSelectedUserInteractor,
                 deviceProvisionedController,
                 faceAuthAccessibilityDelegate,
@@ -786,8 +791,8 @@
 
     @Test
     fun dismissesKeyguard_whenSceneChangesToGone() =
-        sceneTestUtils.testScope.runTest {
-            sceneTestUtils.fakeSceneContainerFlags.enabled = true
+        kosmos.testScope.runTest {
+            kosmos.fakeSceneContainerFlags.enabled = true
             // Upon init, we have never dismisses the keyguard.
             underTest.onInit()
             runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index b4d4e1f..caf9219 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -29,10 +29,13 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
@@ -59,8 +62,8 @@
     @Mock private lateinit var tableLogger: TableLogBuffer
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
 
-    private val testUtils = SceneTestUtils(this)
-    private val testScope = testUtils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private val clock = FakeSystemClock()
     private val userRepository = FakeUserRepository()
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
@@ -82,8 +85,8 @@
         underTest =
             AuthenticationRepositoryImpl(
                 applicationScope = testScope.backgroundScope,
-                backgroundDispatcher = testUtils.testDispatcher,
-                flags = testUtils.fakeSceneContainerFlags,
+                backgroundDispatcher = kosmos.testDispatcher,
+                flags = kosmos.sceneContainerFlags,
                 clock = clock,
                 getSecurityMode = getSecurityMode,
                 userRepository = userRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 10c16bd..cb8cebf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
@@ -29,7 +30,8 @@
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +47,9 @@
 @RunWith(AndroidJUnit4::class)
 class AuthenticationInteractorTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val underTest = utils.authenticationInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.authenticationInteractor
 
     private val onAuthenticationResult by
         testScope.collectLastValue(underTest.onAuthenticationResult)
@@ -62,7 +64,7 @@
             assertThat(authMethod).isEqualTo(Pin)
             assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin)
 
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
 
             assertThat(authMethod).isEqualTo(Password)
             assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password)
@@ -74,7 +76,7 @@
             val authMethod by collectLastValue(underTest.authenticationMethod)
             runCurrent()
 
-            utils.authenticationRepository.setAuthenticationMethod(None)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(None)
 
             assertThat(authMethod).isEqualTo(None)
             assertThat(underTest.getAuthenticationMethod()).isEqualTo(None)
@@ -83,7 +85,7 @@
     @Test
     fun authenticate_withCorrectPin_succeeds() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
 
             assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
         }
@@ -91,7 +93,7 @@
     @Test
     fun authenticate_withIncorrectPin_fails() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
 
             assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
         }
@@ -99,7 +101,7 @@
     @Test(expected = IllegalArgumentException::class)
     fun authenticate_withEmptyPin_throwsException() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             underTest.authenticate(listOf())
         }
 
@@ -107,7 +109,7 @@
     fun authenticate_withCorrectMaxLengthPin_succeeds() =
         testScope.runTest {
             val correctMaxLengthPin = List(16) { 9 }
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 overrideCredential(correctMaxLengthPin)
             }
@@ -124,7 +126,7 @@
             // If the policy changes, there is work to do in SysUI.
             assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
 
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
 
             assertFailed(underTest.authenticate(List(17) { 9 }))
         }
@@ -132,7 +134,7 @@
     @Test
     fun authenticate_withCorrectPassword_succeeds() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
 
             assertSucceeded(underTest.authenticate("password".toList()))
         }
@@ -140,7 +142,7 @@
     @Test
     fun authenticate_withIncorrectPassword_fails() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
 
             assertFailed(underTest.authenticate("alohomora".toList()))
         }
@@ -148,7 +150,7 @@
     @Test
     fun authenticate_withCorrectPattern_succeeds() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pattern)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
 
             assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
         }
@@ -156,7 +158,7 @@
     @Test
     fun authenticate_withIncorrectPattern_fails() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pattern)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
             val wrongPattern =
                 listOf(
                     AuthenticationPatternCoordinate(x = 2, y = 0),
@@ -172,7 +174,7 @@
     fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
@@ -182,14 +184,14 @@
 
             assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true))
             assertThat(underTest.lockoutEndTimestamp).isNull()
-            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+            assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         }
 
     @Test
     fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalse() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
@@ -207,7 +209,7 @@
     fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalse() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
@@ -225,7 +227,7 @@
     fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrue() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
@@ -241,7 +243,7 @@
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
                 reportLockoutStarted(42)
@@ -258,7 +260,7 @@
     @Test
     fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() =
         testScope.runTest {
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(false)
             }
@@ -271,7 +273,7 @@
     @Test
     fun tryAutoConfirm_withoutCorrectPassword_returnsNull() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
 
             assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true))
         }
@@ -280,7 +282,7 @@
     fun isAutoConfirmEnabled_featureDisabled_returnsFalse() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(false)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
 
             assertThat(isAutoConfirmEnabled).isFalse()
         }
@@ -289,7 +291,7 @@
     fun isAutoConfirmEnabled_featureEnabled_returnsTrue() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             assertThat(isAutoConfirmEnabled).isTrue()
         }
@@ -298,7 +300,7 @@
     fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             // The feature is enabled.
             assertThat(isAutoConfirmEnabled).isTrue()
@@ -308,7 +310,7 @@
                 assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN
             }
             assertThat(underTest.lockoutEndTimestamp).isNotNull()
-            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+            assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
 
             // Lockout disabled auto-confirm.
             assertThat(isAutoConfirmEnabled).isFalse()
@@ -336,7 +338,7 @@
             val failedAuthenticationAttempts by
                 collectLastValue(underTest.failedAuthenticationAttempts)
 
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
 
             assertSucceeded(underTest.authenticate(correctPin))
@@ -366,7 +368,7 @@
     @Test
     fun lockoutEndTimestamp() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
 
             underTest.authenticate(correctPin)
@@ -384,7 +386,7 @@
             val expectedLockoutEndTimestamp =
                 testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS
             assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
-            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+            assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
 
             // Correct PIN, but locked out, so doesn't attempt it:
             assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
@@ -409,7 +411,7 @@
     fun upcomingWipe() =
         testScope.runTest {
             val upcomingWipe by collectLastValue(underTest.upcomingWipe)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
             val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
 
@@ -418,7 +420,7 @@
 
             var expectedFailedAttempts = 0
             var remainingFailedAttempts =
-                utils.authenticationRepository.getMaxFailedUnlockAttemptsForWipe()
+                kosmos.fakeAuthenticationRepository.getMaxFailedUnlockAttemptsForWipe()
             assertThat(remainingFailedAttempts)
                 .isGreaterThan(LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE)
 
@@ -458,7 +460,7 @@
     fun hintedPinLength_withoutAutoConfirm_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(false)
             }
@@ -470,11 +472,13 @@
     fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 overrideCredential(
                     buildList {
-                        repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+                        repeat(kosmos.fakeAuthenticationRepository.hintedPinLength - 1) {
+                            add(it + 1)
+                        }
                     }
                 )
                 setAutoConfirmFeatureEnabled(true)
@@ -487,28 +491,31 @@
     fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
                 overrideCredential(
                     buildList {
-                        repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
+                        repeat(kosmos.fakeAuthenticationRepository.hintedPinLength) { add(it + 1) }
                     }
                 )
             }
 
-            assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
+            assertThat(hintedPinLength)
+                .isEqualTo(kosmos.fakeAuthenticationRepository.hintedPinLength)
         }
 
     @Test
     fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 overrideCredential(
                     buildList {
-                        repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+                        repeat(kosmos.fakeAuthenticationRepository.hintedPinLength + 1) {
+                            add(it + 1)
+                        }
                     }
                 )
                 setAutoConfirmFeatureEnabled(true)
@@ -520,10 +527,10 @@
     @Test
     fun authenticate_withTooShortPassword() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
 
             val tooShortPassword = buildList {
-                repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+                repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time ->
                     add("$time")
                 }
             }
@@ -534,7 +541,7 @@
         assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED)
         assertThat(onAuthenticationResult).isTrue()
         assertThat(underTest.lockoutEndTimestamp).isNull()
-        assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+        assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         assertThat(failedAuthenticationAttempts).isEqualTo(0)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 4a39799..72e884e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics
 
+import android.graphics.Bitmap
 import android.hardware.biometrics.BiometricManager.Authenticators
 import android.hardware.biometrics.ComponentInfoInternal
 import android.hardware.biometrics.PromptContentView
@@ -117,6 +118,8 @@
 }
 
 internal fun promptInfo(
+    logoRes: Int = -1,
+    logoBitmap: Bitmap? = null,
     title: String = "title",
     subtitle: String = "sub",
     description: String = "desc",
@@ -127,6 +130,8 @@
     negativeButton: String = "neg",
 ): PromptInfo {
     val info = PromptInfo()
+    info.logoRes = logoRes
+    info.logoBitmap = logoBitmap
     info.title = title
     info.subtitle = subtitle
     info.description = description
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
index c2117ae..a67b093 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
@@ -20,8 +20,11 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -36,8 +39,8 @@
 @RunWith(AndroidJUnit4::class)
 class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var underTest: EmergencyServicesRepository
 
@@ -52,7 +55,7 @@
             EmergencyServicesRepository(
                 resources = context.resources,
                 applicationScope = testScope.backgroundScope,
-                configurationRepository = utils.configurationRepository,
+                configurationRepository = kosmos.configurationRepository,
             )
     }
 
@@ -71,7 +74,7 @@
 
     private fun TestScope.setEmergencyCallWhileSimLocked(isEnabled: Boolean) {
         overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, isEnabled)
-        utils.configurationRepository.onConfigurationChange()
+        kosmos.fakeConfigurationRepository.onConfigurationChange()
         runCurrent()
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 63581b3..741cde8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -25,11 +25,18 @@
 import com.android.internal.logging.nano.MetricsProto
 import com.android.internal.util.emergencyAffordanceManager
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.whenever
 import com.android.telecom.telecomManager
@@ -54,11 +61,11 @@
     @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var telecomManager: TelecomManager
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val metricsLogger = utils.kosmos.fakeMetricsLogger
-    private val activityTaskManager = utils.kosmos.activityTaskManager
-    private val emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val metricsLogger = kosmos.fakeMetricsLogger
+    private val activityTaskManager = kosmos.activityTaskManager
+    private val emergencyAffordanceManager = kosmos.emergencyAffordanceManager
 
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
 
@@ -68,9 +75,9 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        utils.fakeSceneContainerFlags.enabled = true
+        kosmos.fakeSceneContainerFlags.enabled = true
 
-        mobileConnectionsRepository = utils.mobileConnectionsRepository
+        mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
 
         overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL)
         overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL)
@@ -83,18 +90,18 @@
             .thenReturn(needsEmergencyAffordance)
         whenever(telecomManager.isInCall).thenReturn(false)
 
-        utils.fakeFeatureFlags.set(REFACTOR_GETCURRENTUSER, true)
+        kosmos.fakeFeatureFlagsClassic.set(REFACTOR_GETCURRENTUSER, true)
 
-        utils.telephonyRepository.setHasTelephonyRadio(true)
+        kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true)
 
-        utils.kosmos.telecomManager = telecomManager
+        kosmos.telecomManager = telecomManager
     }
 
     @Test
     fun noTelephonyRadio_noButton() =
         testScope.runTest {
-            utils.telephonyRepository.setHasTelephonyRadio(false)
-            val underTest = utils.bouncerActionButtonInteractor()
+            kosmos.fakeTelephonyRepository.setHasTelephonyRadio(false)
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             assertThat(actionButton).isNull()
         }
@@ -102,8 +109,8 @@
     @Test
     fun noTelecomManager_noButton() =
         testScope.runTest {
-            utils.kosmos.telecomManager = null
-            val underTest = utils.bouncerActionButtonInteractor()
+            kosmos.telecomManager = null
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             assertThat(actionButton).isNull()
         }
@@ -111,9 +118,9 @@
     @Test
     fun duringCall_returnToCallButton() =
         testScope.runTest {
-            val underTest = utils.bouncerActionButtonInteractor()
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
-            utils.telephonyRepository.setIsInCall(true)
+            kosmos.fakeTelephonyRepository.setIsInCall(true)
 
             assertThat(actionButton).isNotNull()
             assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL)
@@ -133,11 +140,13 @@
     @Test
     fun noCall_secureAuthMethod_emergencyCallButton() =
         testScope.runTest {
-            val underTest = utils.bouncerActionButtonInteractor()
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = false
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.telephonyRepository.setIsInCall(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeTelephonyRepository.setIsInCall(false)
 
             assertThat(actionButton).isNotNull()
             assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
@@ -163,11 +172,13 @@
     @Test
     fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() =
         testScope.runTest {
-            val underTest = utils.bouncerActionButtonInteractor()
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = true
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.telephonyRepository.setIsInCall(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            kosmos.fakeTelephonyRepository.setIsInCall(false)
             runCurrent()
 
             assertThat(actionButton).isNotNull()
@@ -179,11 +190,13 @@
     @Test
     fun noCall_insecure_noButton() =
         testScope.runTest {
-            val underTest = utils.bouncerActionButtonInteractor()
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = false
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.telephonyRepository.setIsInCall(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            kosmos.fakeTelephonyRepository.setIsInCall(false)
 
             assertThat(actionButton).isNull()
         }
@@ -191,13 +204,15 @@
     @Test
     fun noCall_simSecureButEmergencyNotSupported_noButton() =
         testScope.runTest {
-            val underTest = utils.bouncerActionButtonInteractor()
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = true
             overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, false)
-            utils.configurationRepository.onConfigurationChange()
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.telephonyRepository.setIsInCall(false)
+            kosmos.fakeConfigurationRepository.onConfigurationChange()
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            kosmos.fakeTelephonyRepository.setIsInCall(false)
             runCurrent()
 
             assertThat(actionButton).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 4b6199b..707777b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -20,15 +20,20 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +50,9 @@
 @RunWith(AndroidJUnit4::class)
 class BouncerInteractorTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
-    private val testScope = utils.testScope
-    private val authenticationInteractor = utils.authenticationInteractor()
+    private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    private val testScope = kosmos.testScope
+    private val authenticationInteractor = kosmos.authenticationInteractor
 
     private lateinit var underTest: BouncerInteractor
 
@@ -62,7 +67,7 @@
         overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD)
         overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN)
 
-        underTest = utils.bouncerInteractor()
+        underTest = kosmos.bouncerInteractor
     }
 
     @Test
@@ -70,7 +75,9 @@
         testScope.runTest {
             val message by collectLastValue(underTest.message)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
             runCurrent()
             underTest.clearMessage()
             assertThat(message).isNull()
@@ -94,7 +101,9 @@
     @Test
     fun pinAuthMethod_sim_skipsAuthentication() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Sim
+            )
             runCurrent()
 
             // We rely on TelephonyManager to authenticate the sim card.
@@ -109,9 +118,11 @@
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
             runCurrent()
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             assertThat(isAutoConfirmEnabled).isTrue()
 
             // Incomplete input.
@@ -137,7 +148,9 @@
         testScope.runTest {
             val message by collectLastValue(underTest.message)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
             runCurrent()
 
             // Incomplete input.
@@ -160,7 +173,7 @@
     fun passwordAuthMethod() =
         testScope.runTest {
             val message by collectLastValue(underTest.message)
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             runCurrent()
@@ -180,7 +193,8 @@
             assertThat(
                     underTest.authenticate(
                         buildList {
-                            repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+                            repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time
+                                ->
                                 add("$time")
                             }
                         }
@@ -198,7 +212,7 @@
     fun patternAuthMethod() =
         testScope.runTest {
             val message by collectLastValue(underTest.message)
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern
             )
             runCurrent()
@@ -214,7 +228,8 @@
                     AuthenticationPatternCoordinate(0, 1),
                 )
             assertThat(wrongPattern).isNotEqualTo(FakeAuthenticationRepository.PATTERN)
-            assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength)
+            assertThat(wrongPattern.size)
+                .isAtLeast(kosmos.fakeAuthenticationRepository.minPatternLength)
             assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED)
             assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
 
@@ -225,7 +240,7 @@
             val tooShortPattern =
                 FakeAuthenticationRepository.PATTERN.subList(
                     0,
-                    utils.authenticationRepository.minPatternLength - 1
+                    kosmos.fakeAuthenticationRepository.minPatternLength - 1
                 )
             assertThat(underTest.authenticate(tooShortPattern))
                 .isEqualTo(AuthenticationResult.SKIPPED)
@@ -245,7 +260,9 @@
             val lockoutStartedEvents by collectValues(underTest.onLockoutStarted)
             val message by collectLastValue(underTest.message)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
             assertThat(lockoutStartedEvents).isEmpty()
 
             // Try the wrong PIN repeatedly, until lockout is triggered:
@@ -291,17 +308,17 @@
     @Test
     fun intentionalUserInputEvent_registersTouchEvent() =
         testScope.runTest {
-            assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+            assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
             underTest.onIntentionalUserInput()
-            assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+            assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
         }
 
     @Test
     fun intentionalUserInputEvent_notifiesFaceAuthInteractor() =
         testScope.runTest {
             val isFaceAuthRunning by
-                collectLastValue(utils.kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning)
-            utils.kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted()
+                collectLastValue(kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning)
+            kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted()
             runCurrent()
             assertThat(isFaceAuthRunning).isTrue()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
index 8c53c0e..09fdd11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -28,8 +28,11 @@
 import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor.Companion.INVALID_SUBSCRIPTION_ID
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -54,10 +57,10 @@
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock lateinit var euiccManager: EuiccManager
 
-    private val utils = SceneTestUtils(this)
+    private val kosmos = testKosmos()
     private val bouncerSimRepository = FakeSimBouncerRepository()
     private val resources: Resources = context.resources
-    private val testScope = utils.testScope
+    private val testScope = kosmos.testScope
 
     private lateinit var underTest: SimBouncerInteractor
 
@@ -68,13 +71,13 @@
             SimBouncerInteractor(
                 context,
                 testScope.backgroundScope,
-                utils.testDispatcher,
+                kosmos.testDispatcher,
                 bouncerSimRepository,
                 telephonyManager,
                 resources,
                 keyguardUpdateMonitor,
                 euiccManager,
-                utils.mobileConnectionsRepository,
+                kosmos.mobileConnectionsRepository,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 3043a71..27b84b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -20,9 +20,13 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runTest
@@ -33,16 +37,16 @@
 @RunWith(AndroidJUnit4::class)
 class AuthMethodBouncerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val bouncerInteractor = utils.bouncerInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val bouncerInteractor = kosmos.bouncerInteractor
     private val underTest =
         PinBouncerViewModel(
             applicationContext = context,
             viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true),
-            simBouncerInteractor = utils.simBouncerInteractor,
+            simBouncerInteractor = kosmos.simBouncerInteractor,
             authenticationMethod = AuthenticationMethodModel.Pin,
         )
 
@@ -50,7 +54,9 @@
     fun animateFailure() =
         testScope.runTest {
             val animateFailure by collectLastValue(underTest.animateFailure)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
             assertThat(animateFailure).isFalse()
 
             // Wrong PIN:
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 4b1f9fe..cfe8c5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -20,15 +20,21 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlin.time.Duration.Companion.seconds
@@ -50,16 +56,16 @@
 @RunWith(AndroidJUnit4::class)
 class BouncerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val bouncerInteractor = utils.bouncerInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val authenticationInteractor = kosmos.authenticationInteractor
+    private val bouncerInteractor = kosmos.bouncerInteractor
     private lateinit var underTest: BouncerViewModel
 
     @Before
     fun setUp() {
-        utils.fakeSceneContainerFlags.enabled = true
-        underTest = utils.bouncerViewModel()
+        kosmos.fakeSceneContainerFlags.enabled = true
+        underTest = kosmos.bouncerViewModel
     }
 
     @Test
@@ -68,7 +74,7 @@
             var authMethodViewModel: AuthMethodBouncerViewModel? = null
 
             authMethodsToTest().forEach { authMethod ->
-                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
                 val job =
                     underTest.authMethodViewModel.onEach { authMethodViewModel = it }.launchIn(this)
                 runCurrent()
@@ -98,13 +104,13 @@
 
             // First pass, populate our "seen" map:
             authMethodsToTest().forEach { authMethod ->
-                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let { seen[authMethod] = it }
             }
 
             // Second pass, assert same instances are not reused:
             authMethodsToTest().forEach { authMethod ->
-                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let {
                     assertThat(it.authenticationMethod).isEqualTo(authMethod)
                     assertThat(it).isNotSameInstanceAs(seen[authMethod])
@@ -116,11 +122,11 @@
     fun authMethodUnchanged_reusesInstances() =
         testScope.runTest {
             authMethodsToTest().forEach { authMethod ->
-                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
                 val firstInstance: AuthMethodBouncerViewModel? =
                     collectLastValue(underTest.authMethodViewModel).invoke()
 
-                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
                 val secondInstance: AuthMethodBouncerViewModel? =
                     collectLastValue(underTest.authMethodViewModel).invoke()
 
@@ -139,7 +145,7 @@
     fun message() =
         testScope.runTest {
             val message by collectLastValue(underTest.message)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(message?.isUpdateAnimated).isTrue()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -157,8 +163,8 @@
         testScope.runTest {
             val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
             val message by collectLastValue(underTest.message)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
-            assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull()
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
+            assertThat(kosmos.fakeAuthenticationRepository.lockoutEndTimestamp).isNull()
             assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
@@ -192,7 +198,7 @@
                         authViewModel?.isInputEnabled ?: emptyFlow()
                     }
                 )
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(isInputEnabled).isTrue()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -210,7 +216,7 @@
         testScope.runTest {
             val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
             val dialogViewModel by collectLastValue(underTest.dialogViewModel)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -228,17 +234,17 @@
     fun isSideBySideSupported() =
         testScope.runTest {
             val isSideBySideSupported by collectLastValue(underTest.isSideBySideSupported)
-            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(isSideBySideSupported).isTrue()
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
             assertThat(isSideBySideSupported).isTrue()
 
-            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(isSideBySideSupported).isTrue()
 
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
             assertThat(isSideBySideSupported).isFalse()
         }
 
@@ -246,12 +252,12 @@
     fun isFoldSplitRequired() =
         testScope.runTest {
             val isFoldSplitRequired by collectLastValue(underTest.isFoldSplitRequired)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(isFoldSplitRequired).isTrue()
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
             assertThat(isFoldSplitRequired).isFalse()
 
-            utils.authenticationRepository.setAuthenticationMethod(Pattern)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
             assertThat(isFoldSplitRequired).isTrue()
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 5c5632f..b3b6457 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,13 +19,19 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,12 +49,12 @@
 @RunWith(AndroidJUnit4::class)
 class PasswordBouncerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val sceneInteractor = utils.sceneInteractor()
-    private val bouncerInteractor = utils.bouncerInteractor()
-    private val bouncerViewModel = utils.bouncerViewModel()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val authenticationInteractor = kosmos.authenticationInteractor
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val bouncerInteractor = kosmos.bouncerInteractor
+    private val bouncerViewModel = kosmos.bouncerViewModel
     private val isInputEnabled = MutableStateFlow(true)
 
     private val underTest =
@@ -140,10 +146,10 @@
         testScope.runTest {
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             switchToScene(SceneKey.Bouncer)
 
             // No input entered.
@@ -309,8 +315,10 @@
     }
 
     private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
-        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
-        utils.deviceEntryRepository.setUnlocked(false)
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+            AuthenticationMethodModel.Password
+        )
+        kosmos.fakeDeviceEntryRepository.setUnlocked(false)
         switchToScene(SceneKey.Bouncer)
     }
 
@@ -320,13 +328,13 @@
     ) {
         if (isLockedOut) {
             repeat(failedAttemptCount) {
-                utils.authenticationRepository.reportAuthenticationAttempt(false)
+                kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false)
             }
-            utils.authenticationRepository.reportLockoutStarted(
+            kosmos.fakeAuthenticationRepository.reportLockoutStarted(
                 30.seconds.inWholeMilliseconds.toInt()
             )
         } else {
-            utils.authenticationRepository.reportAuthenticationAttempt(true)
+            kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(true)
         }
         isInputEnabled.value = !isLockedOut
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 9ee344a..c2680bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -20,13 +20,20 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.authenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,12 +51,12 @@
 @RunWith(AndroidJUnit4::class)
 class PatternBouncerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val sceneInteractor = utils.sceneInteractor()
-    private val bouncerInteractor = utils.bouncerInteractor()
-    private val bouncerViewModel = utils.bouncerViewModel()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val authenticationInteractor = kosmos.authenticationInteractor
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val bouncerInteractor = kosmos.bouncerInteractor
+    private val bouncerViewModel = kosmos.bouncerViewModel
     private val underTest =
         PatternBouncerViewModel(
             applicationContext = context,
@@ -305,7 +312,7 @@
                 underTest.onDragStart()
                 CORRECT_PATTERN.subList(
                         0,
-                        utils.authenticationRepository.minPatternLength - 1,
+                        kosmos.authenticationRepository.minPatternLength - 1,
                     )
                     .forEach { coordinate ->
                         underTest.onDrag(
@@ -374,8 +381,10 @@
     }
 
     private fun TestScope.lockDeviceAndOpenPatternBouncer() {
-        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
-        utils.deviceEntryRepository.setUnlocked(false)
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+            AuthenticationMethodModel.Pattern
+        )
+        kosmos.fakeDeviceEntryRepository.setUnlocked(false)
         switchToScene(SceneKey.Bouncer)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 75e372f..1d660d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -20,12 +20,20 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,19 +51,19 @@
 @RunWith(AndroidJUnit4::class)
 class PinBouncerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val bouncerInteractor = utils.bouncerInteractor()
-    private val bouncerViewModel = utils.bouncerViewModel()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val authenticationInteractor = kosmos.authenticationInteractor
+    private val bouncerInteractor = kosmos.bouncerInteractor
+    private val bouncerViewModel = kosmos.bouncerViewModel
     private val underTest =
         PinBouncerViewModel(
             applicationContext = context,
             viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
-            simBouncerInteractor = utils.simBouncerInteractor,
+            simBouncerInteractor = kosmos.simBouncerInteractor,
             authenticationMethod = AuthenticationMethodModel.Pin,
         )
 
@@ -86,7 +94,7 @@
                     viewModelScope = testScope.backgroundScope,
                     interactor = bouncerInteractor,
                     isInputEnabled = MutableStateFlow(true).asStateFlow(),
-                    simBouncerInteractor = utils.simBouncerInteractor,
+                    simBouncerInteractor = kosmos.simBouncerInteractor,
                     authenticationMethod = AuthenticationMethodModel.Sim,
                 )
 
@@ -97,7 +105,7 @@
     fun onErrorDialogDismissed_clearsDialogMessage() =
         testScope.runTest {
             val dialogMessage by collectLastValue(underTest.errorDialogMessage)
-            utils.simBouncerRepository.setSimVerificationErrorMessage("abc")
+            kosmos.fakeSimBouncerRepository.setSimVerificationErrorMessage("abc")
             assertThat(dialogMessage).isEqualTo("abc")
 
             underTest.onErrorDialogDismissed()
@@ -114,10 +122,10 @@
                     viewModelScope = testScope.backgroundScope,
                     interactor = bouncerInteractor,
                     isInputEnabled = MutableStateFlow(true).asStateFlow(),
-                    simBouncerInteractor = utils.simBouncerInteractor,
+                    simBouncerInteractor = kosmos.simBouncerInteractor,
                     authenticationMethod = AuthenticationMethodModel.Sim,
                 )
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
 
             assertThat(hintedPinLength).isNull()
@@ -254,7 +262,7 @@
     @Test
     fun onAutoConfirm_whenCorrect() =
         testScope.runTest {
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
             lockDeviceAndOpenPinBouncer()
 
@@ -269,7 +277,7 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             lockDeviceAndOpenPinBouncer()
 
             FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
@@ -309,7 +317,9 @@
         testScope.runTest {
             val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
 
             assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
         }
@@ -318,8 +328,10 @@
     fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
         testScope.runTest {
             val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
         }
@@ -328,8 +340,10 @@
     fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
         testScope.runTest {
             val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             underTest.onPinButtonClicked(1)
 
@@ -341,7 +355,9 @@
         testScope.runTest {
             val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
 
             assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
         }
@@ -350,8 +366,10 @@
     fun confirmButtonAppearance_withAutoConfirm_isHidden() =
         testScope.runTest {
             val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
         }
@@ -361,10 +379,10 @@
         testScope.runTest {
             val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled)
 
-            utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true)
+            kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(true)
             assertThat(isAnimationEnabled).isFalse()
 
-            utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false)
+            kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(false)
             assertThat(isAnimationEnabled).isTrue()
         }
 
@@ -382,8 +400,8 @@
     }
 
     private fun TestScope.lockDeviceAndOpenPinBouncer() {
-        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-        utils.deviceEntryRepository.setUnlocked(false)
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+        kosmos.fakeDeviceEntryRepository.setUnlocked(false)
         switchToScene(SceneKey.Bouncer)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 65176e1..81d5344 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -24,11 +24,12 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.data.repository.sceneContainerRepository
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -51,8 +52,8 @@
 
     @Before
     fun setUp() {
-        val sceneTestUtils = SceneTestUtils(this)
-        sceneContainerRepository = sceneTestUtils.fakeSceneContainerRepository()
+        val kosmos = testKosmos()
+        sceneContainerRepository = kosmos.sceneContainerRepository
         featureFlagsClassic = FakeFeatureFlagsClassic()
 
         featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
new file mode 100644
index 0000000..c4a8582
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.communal.data.repository
+
+import android.content.pm.UserInfo
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryImpl.Companion.CURRENT_TUTORIAL_VERSION
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var secureSettings: FakeSettings
+    private lateinit var userRepository: FakeUserRepository
+
+    private lateinit var underTest: CommunalTutorialRepositoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        secureSettings = FakeSettings()
+        userRepository = FakeUserRepository()
+        val listOfUserInfo = listOf(MAIN_USER_INFO)
+        userRepository.setUserInfos(listOfUserInfo)
+
+        underTest =
+            CommunalTutorialRepositoryImpl(
+                kosmos.applicationCoroutineScope,
+                kosmos.testDispatcher,
+                userRepository,
+                secureSettings,
+                logcatLogBuffer("CommunalTutorialRepositoryImplTest"),
+            )
+    }
+
+    @Test
+    fun tutorialSettingState_defaultToNotStarted() =
+        testScope.runTest {
+            val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+            assertThat(tutorialSettingState)
+                .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+        }
+
+    @Test
+    fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() =
+        testScope.runTest {
+            underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
+            val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
+        }
+
+    @Test
+    fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() =
+        testScope.runTest {
+            underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+            val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+        }
+
+    @Test
+    fun tutorialVersion_userCompletedCurrentVersion_stateCompleted() =
+        testScope.runTest {
+            // User completed the current version.
+            setTutorialStateSetting(CURRENT_TUTORIAL_VERSION)
+
+            // Verify tutorial state is completed.
+            val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+        }
+
+    @Test
+    fun tutorialVersion_userCompletedPreviousVersion_stateNotStarted() =
+        testScope.runTest {
+            // User completed the previous version.
+            setTutorialStateSetting(CURRENT_TUTORIAL_VERSION - 1)
+
+            // Verify tutorial state is not started.
+            val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+            assertThat(tutorialSettingState)
+                .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+        }
+
+    @Test
+    fun tutorialVersion_uponTutorialCompletion_writeCurrentVersion() =
+        testScope.runTest {
+            // Tutorial not started.
+            setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+
+            // Tutorial completed.
+            underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Verify tutorial setting state is updated to current version.
+            val settingState = getTutorialStateSetting()
+            assertThat(settingState).isEqualTo(CURRENT_TUTORIAL_VERSION)
+        }
+
+    private fun setTutorialStateSetting(
+        @Settings.Secure.HubModeTutorialState state: Int,
+        user: UserInfo = MAIN_USER_INFO
+    ) {
+        secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id)
+    }
+
+    private fun getTutorialStateSetting(user: UserInfo = MAIN_USER_INFO): Int {
+        return secureSettings.getIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, user.id)
+    }
+
+    companion object {
+        private val MAIN_USER_INFO =
+            UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 4079f12..fdb17c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -16,14 +16,9 @@
 
 package com.android.systemui.communal.data.repository
 
-import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
-import android.content.Intent
-import android.content.Intent.ACTION_USER_UNLOCKED
-import android.os.UserHandle
-import android.os.UserManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -32,11 +27,14 @@
 import com.android.systemui.communal.data.db.CommunalWidgetItem
 import com.android.systemui.communal.shared.CommunalWidgetHost
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -44,8 +42,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -65,13 +61,7 @@
 
     @Mock private lateinit var appWidgetManager: AppWidgetManager
 
-    @Mock private lateinit var appWidgetHost: AppWidgetHost
-
-    @Mock private lateinit var userManager: UserManager
-
-    @Mock private lateinit var userHandle: UserHandle
-
-    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
 
     @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
 
@@ -81,14 +71,12 @@
 
     @Mock private lateinit var communalWidgetDao: CommunalWidgetDao
 
-    private lateinit var communalRepository: FakeCommunalRepository
+    private val kosmos = testKosmos()
+
+    private val testScope = kosmos.testScope
 
     private lateinit var logBuffer: LogBuffer
 
-    private val testDispatcher = StandardTestDispatcher()
-
-    private val testScope = TestScope(testDispatcher)
-
     private val fakeAllowlist =
         listOf(
             "com.android.fake/WidgetProviderA",
@@ -96,68 +84,60 @@
             "com.android.fake/WidgetProviderC",
         )
 
+    private lateinit var underTest: CommunalWidgetRepositoryImpl
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        logBuffer = FakeLogBuffer.Factory.create()
-        communalRepository = FakeCommunalRepository()
+        logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
 
-        communalEnabled(true)
         setAppWidgetIds(emptyList())
 
         overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
 
         whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
-        whenever(userTracker.userHandle).thenReturn(userHandle)
         whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
         whenever(appWidgetManagerOptional.isPresent).thenReturn(true)
         whenever(appWidgetManagerOptional.get()).thenReturn(appWidgetManager)
+
+        underTest =
+            CommunalWidgetRepositoryImpl(
+                appWidgetManagerOptional,
+                appWidgetHost,
+                testScope.backgroundScope,
+                kosmos.testDispatcher,
+                communalWidgetHost,
+                communalWidgetDao,
+                logBuffer,
+            )
     }
 
     @Test
-    fun neverQueryDbForWidgets_whenFeatureIsDisabled() =
+    fun neverQueryDbForWidgets_whenHostIsInactive() =
         testScope.runTest {
-            communalEnabled(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
+            underTest.updateAppWidgetHostActive(false)
+            underTest.communalWidgets.launchIn(testScope.backgroundScope)
             runCurrent()
 
             verify(communalWidgetDao, never()).getWidgets()
         }
 
     @Test
-    fun neverQueryDbForWidgets_whenFeatureEnabled_andUserLocked() =
+    fun communalWidgets_whenHostIsActive_queryWidgetsFromDb() =
         testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
+            underTest.updateAppWidgetHostActive(true)
 
-            verify(communalWidgetDao, never()).getWidgets()
-        }
-
-    @Test
-    fun communalWidgets_whenUserUnlocked_queryWidgetsFromDb() =
-        testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-            val communalWidgets by collectLastValue(repository.communalWidgets)
-            runCurrent()
             val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
             val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
             whenever(communalWidgetDao.getWidgets())
                 .thenReturn(flowOf(mapOf(communalItemRankEntry to communalWidgetItemEntry)))
             whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
 
-            userUnlocked(true)
             installedProviders(listOf(stopwatchProviderInfo))
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(ACTION_USER_UNLOCKED)
-            )
-            runCurrent()
 
+            val communalWidgets by collectLastValue(underTest.communalWidgets)
+            runCurrent()
             verify(communalWidgetDao).getWidgets()
             assertThat(communalWidgets)
                 .containsExactly(
@@ -172,9 +152,7 @@
     @Test
     fun addWidget_allocateId_bindWidget_andAddToDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
+            underTest.updateAppWidgetHostActive(true)
 
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
@@ -182,7 +160,7 @@
             whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
             whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
                 .thenReturn(id)
-            repository.addWidget(provider, priority) { true }
+            underTest.addWidget(provider, priority) { true }
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -192,16 +170,14 @@
     @Test
     fun addWidget_configurationFails_doNotAddWidgetToDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
+            underTest.updateAppWidgetHostActive(true)
 
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
             whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
             whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
-            repository.addWidget(provider, priority) { false }
+            underTest.addWidget(provider, priority) { false }
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -212,16 +188,14 @@
     @Test
     fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
+            underTest.updateAppWidgetHostActive(true)
 
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
             whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
             whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
-            repository.addWidget(provider, priority) { throw IllegalStateException("some error") }
+            underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -232,9 +206,7 @@
     @Test
     fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
+            underTest.updateAppWidgetHostActive(true)
 
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
@@ -243,7 +215,7 @@
             whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
                 .thenReturn(id)
             var configured = false
-            repository.addWidget(provider, priority) {
+            underTest.addWidget(provider, priority) {
                 configured = true
                 true
             }
@@ -257,12 +229,10 @@
     @Test
     fun deleteWidget_removeWidgetId_andDeleteFromDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
+            underTest.updateAppWidgetHostActive(true)
 
             val id = 1
-            repository.deleteWidget(id)
+            underTest.deleteWidget(id)
             runCurrent()
 
             verify(communalWidgetDao).deleteWidgetById(id)
@@ -272,108 +242,37 @@
     @Test
     fun reorderWidgets_queryDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
+            underTest.updateAppWidgetHostActive(true)
 
             val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
-            repository.updateWidgetOrder(widgetIdToPriorityMap)
+            underTest.updateWidgetOrder(widgetIdToPriorityMap)
             runCurrent()
 
             verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
         }
 
     @Test
-    fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+    fun appWidgetHost_startListening() =
         testScope.runTest {
-            communalEnabled(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
-            assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
-        }
-
-    @Test
-    fun broadcastReceiver_featureEnabledAndUserLocked_registerBroadcastReceiver() =
-        testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
-            assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
-        }
-
-    @Test
-    fun appWidgetHost_userUnlocked_startListening() =
-        testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
             verify(appWidgetHost, never()).startListening()
 
-            userUnlocked(true)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(ACTION_USER_UNLOCKED)
-            )
-            runCurrent()
+            underTest.updateAppWidgetHostActive(true)
 
             verify(appWidgetHost).startListening()
         }
 
     @Test
-    fun appWidgetHost_userLockedAgain_stopListening() =
+    fun appWidgetHost_stopListening() =
         testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
-
-            userUnlocked(true)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(ACTION_USER_UNLOCKED)
-            )
-            runCurrent()
+            underTest.updateAppWidgetHostActive(true)
 
             verify(appWidgetHost).startListening()
-            verify(appWidgetHost, never()).stopListening()
 
-            userUnlocked(false)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(ACTION_USER_UNLOCKED)
-            )
-            runCurrent()
+            underTest.updateAppWidgetHostActive(false)
 
             verify(appWidgetHost).stopListening()
         }
 
-    private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
-        return CommunalWidgetRepositoryImpl(
-            appWidgetManagerOptional,
-            appWidgetHost,
-            testScope.backgroundScope,
-            testDispatcher,
-            fakeBroadcastDispatcher,
-            communalRepository,
-            communalWidgetHost,
-            communalWidgetDao,
-            userManager,
-            userTracker,
-            logBuffer,
-        )
-    }
-
-    private fun communalEnabled(enabled: Boolean) {
-        communalRepository.setIsCommunalEnabled(enabled)
-    }
-
-    private fun userUnlocked(userUnlocked: Boolean) {
-        whenever(userManager.isUserUnlockingOrUnlocked(userHandle)).thenReturn(userUnlocked)
-    }
-
     private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
         whenever(appWidgetManager.installedProviders).thenReturn(providers)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
new file mode 100644
index 0000000..ed29aa4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.communalInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This class is a variation of the [CommunalInteractorTest] for cases where communal is disabled.
+ */
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var communalRepository: FakeCommunalRepository
+    private lateinit var widgetRepository: FakeCommunalWidgetRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+
+    private lateinit var underTest: CommunalInteractor
+
+    @Before
+    fun setUp() {
+        communalRepository = kosmos.fakeCommunalRepository
+        widgetRepository = kosmos.fakeCommunalWidgetRepository
+        keyguardRepository = kosmos.fakeKeyguardRepository
+
+        communalRepository.setIsCommunalEnabled(false)
+
+        underTest = kosmos.communalInteractor
+    }
+
+    @Test
+    fun isCommunalEnabled_false() =
+        testScope.runTest { assertThat(underTest.isCommunalEnabled).isFalse() }
+
+    @Test
+    fun isCommunalAvailable_whenStorageUnlock_false() =
+        testScope.runTest {
+            val isCommunalAvailable by collectLastValue(underTest.isCommunalAvailable)
+
+            assertThat(isCommunalAvailable).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            runCurrent()
+
+            assertThat(isCommunalAvailable).isFalse()
+        }
+
+    @Test
+    fun updateAppWidgetHostActive_whenStorageUnlock_false() =
+        testScope.runTest {
+            assertThat(widgetRepository.isHostActive()).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            runCurrent()
+
+            assertThat(widgetRepository.isHostActive()).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 178eb6a..7769223 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -28,6 +28,11 @@
 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.fakeCommunalPrefsRepository
+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.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalSceneKey
@@ -36,15 +41,19 @@
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.domain.interactor.editWidgetsActivityStarter
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -52,13 +61,17 @@
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 
+/**
+ * This class of test cases assume that communal is enabled. For disabled cases, see
+ * [CommunalInteractorCommunalDisabledTest].
+ */
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class CommunalInteractorTest : SysuiTestCase() {
-    private lateinit var testScope: TestScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var tutorialRepository: FakeCommunalTutorialRepository
     private lateinit var communalRepository: FakeCommunalRepository
@@ -73,36 +86,56 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
+        tutorialRepository = kosmos.fakeCommunalTutorialRepository
+        communalRepository = kosmos.fakeCommunalRepository
+        mediaRepository = kosmos.fakeCommunalMediaRepository
+        widgetRepository = kosmos.fakeCommunalWidgetRepository
+        smartspaceRepository = kosmos.fakeSmartspaceRepository
+        keyguardRepository = kosmos.fakeKeyguardRepository
+        editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
+        communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
 
-        testScope = TestScope(StandardTestDispatcher())
-
-        val withDeps = CommunalInteractorFactory.create(testScope)
-
-        tutorialRepository = withDeps.tutorialRepository
-        communalRepository = withDeps.communalRepository
-        mediaRepository = withDeps.mediaRepository
-        widgetRepository = withDeps.widgetRepository
-        smartspaceRepository = withDeps.smartspaceRepository
-        keyguardRepository = withDeps.keyguardRepository
-        editWidgetsActivityStarter = withDeps.editWidgetsActivityStarter
-        communalPrefsRepository = withDeps.communalPrefsRepository
-
-        underTest = withDeps.communalInteractor
+        underTest = kosmos.communalInteractor
     }
 
     @Test
-    fun communalEnabled() =
+    fun communalEnabled_true() =
+        testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() }
+
+    @Test
+    fun isCommunalAvailable_trueWhenStorageUnlock() =
         testScope.runTest {
-            communalRepository.setIsCommunalEnabled(true)
-            assertThat(underTest.isCommunalEnabled).isTrue()
+            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+            assertThat(isAvailable).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            runCurrent()
+
+            assertThat(isAvailable).isTrue()
         }
 
     @Test
-    fun communalDisabled() =
+    fun isCommunalAvailable_whenStorageUnlock_true() =
         testScope.runTest {
-            communalRepository.setIsCommunalEnabled(false)
-            assertThat(underTest.isCommunalEnabled).isFalse()
+            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+            assertThat(isAvailable).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            runCurrent()
+
+            assertThat(isAvailable).isTrue()
+        }
+
+    @Test
+    fun updateAppWidgetHostActive_uponStorageUnlock_true() =
+        testScope.runTest {
+            collectLastValue(underTest.isCommunalAvailable)
+            assertThat(widgetRepository.isHostActive()).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            runCurrent()
+
+            assertThat(widgetRepository.isHostActive()).isTrue()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
new file mode 100644
index 0000000..e904236
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.widgets
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAppWidgetHostTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: CommunalAppWidgetHost
+
+    @Before
+    fun setUp() {
+        underTest = CommunalAppWidgetHost(context = context, hostId = 116)
+    }
+
+    @Test
+    fun createViewForCommunal_returnCommunalAppWidgetView() =
+        testScope.runTest {
+            val appWidgetId = 789
+            val view =
+                underTest.createViewForCommunal(
+                    context = context,
+                    appWidgetId = appWidgetId,
+                    appWidget = null
+                )
+            assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java)
+            assertThat(view).isNotNull()
+            assertThat(view.appWidgetId).isEqualTo(appWidgetId)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 54510a8..09243e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -19,7 +19,6 @@
 import android.app.Activity.RESULT_CANCELED
 import android.app.Activity.RESULT_OK
 import android.app.smartspace.SmartspaceTarget
-import android.appwidget.AppWidgetHost
 import android.content.ComponentName
 import android.provider.Settings
 import android.widget.RemoteViews
@@ -36,6 +35,7 @@
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
@@ -61,7 +61,7 @@
 @RunWith(AndroidJUnit4::class)
 class CommunalEditModeViewModelTest : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
-    @Mock private lateinit var appWidgetHost: AppWidgetHost
+    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
     @Mock private lateinit var uiEventLogger: UiEventLogger
 
     private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 565049b..b54c5bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -7,9 +7,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
@@ -36,8 +38,8 @@
     @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardStateController: KeyguardStateController
 
-    private val testUtils = SceneTestUtils(this)
-    private val testScope = testUtils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private val userRepository = FakeUserRepository()
     private val keyguardRepository = FakeKeyguardRepository()
 
@@ -52,7 +54,7 @@
         underTest =
             DeviceEntryRepositoryImpl(
                 applicationScope = testScope.backgroundScope,
-                backgroundDispatcher = testUtils.testDispatcher,
+                backgroundDispatcher = kosmos.testDispatcher,
                 userRepository = userRepository,
                 lockPatternUtils = lockPatternUtils,
                 keyguardBypassController = keyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 929e879..62d2315 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -20,14 +20,20 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -41,18 +47,18 @@
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryInteractorTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository
-    private val trustRepository = utils.kosmos.fakeTrustRepository
-    private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor = utils.authenticationInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+    private val trustRepository = kosmos.fakeTrustRepository
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val authenticationInteractor = kosmos.authenticationInteractor
     private lateinit var underTest: DeviceEntryInteractor
 
     @Before
     fun setUp() {
-        utils.fakeSceneContainerFlags.enabled = true
-        underTest = utils.deviceEntryInteractor()
+        kosmos.fakeSceneContainerFlags.enabled = true
+        underTest = kosmos.deviceEntryInteractor
     }
 
     @Test
@@ -65,8 +71,10 @@
     @Test
     fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.deviceEntryRepository.apply {
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            kosmos.fakeDeviceEntryRepository.apply {
                 setLockscreenEnabled(false)
 
                 // Toggle isUnlocked, twice.
@@ -99,8 +107,10 @@
     @Test
     fun isUnlocked_whenAuthMethodIsSimAndUnlocked_isFalse() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Sim
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             assertThat(isUnlocked).isFalse()
@@ -157,10 +167,10 @@
     @Test
     fun isDeviceEntered_onBouncer_isFalse() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern
             )
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             switchToScene(SceneKey.Lockscreen)
             runCurrent()
             switchToScene(SceneKey.Bouncer)
@@ -182,8 +192,10 @@
     @Test
     fun canSwipeToEnter_onLockscreenWithPin_isFalse() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             switchToScene(SceneKey.Lockscreen)
 
             val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
@@ -203,15 +215,15 @@
         }
 
     private fun setupSwipeDeviceEntryMethod() {
-        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-        utils.deviceEntryRepository.setLockscreenEnabled(true)
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+        kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
     }
 
     @Test
     fun canSwipeToEnter_whenTrustedByTrustManager_isTrue() =
         testScope.runTest {
             val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             switchToScene(SceneKey.Lockscreen)
@@ -228,7 +240,7 @@
     fun canSwipeToEnter_whenAuthenticatedByFace_isTrue() =
         testScope.runTest {
             val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             switchToScene(SceneKey.Lockscreen)
@@ -244,9 +256,9 @@
     @Test
     fun isAuthenticationRequired_lockedAndSecured_true() =
         testScope.runTest {
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
 
@@ -256,9 +268,11 @@
     @Test
     fun isAuthenticationRequired_lockedAndNotSecured_false() =
         testScope.runTest {
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -266,9 +280,9 @@
     @Test
     fun isAuthenticationRequired_unlockedAndSecured_false() =
         testScope.runTest {
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
 
@@ -278,9 +292,11 @@
     @Test
     fun isAuthenticationRequired_unlockedAndNotSecured_false() =
         testScope.runTest {
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -288,7 +304,7 @@
     @Test
     fun isBypassEnabled_enabledInRepository_true() =
         testScope.runTest {
-            utils.deviceEntryRepository.setBypassEnabled(true)
+            kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
             assertThat(underTest.isBypassEnabled.value).isTrue()
         }
 
@@ -299,8 +315,10 @@
             switchToScene(SceneKey.Lockscreen)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
 
             underTest.attemptDeviceEntry()
@@ -315,7 +333,9 @@
             switchToScene(SceneKey.Lockscreen)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
 
             underTest.attemptDeviceEntry()
 
@@ -329,8 +349,10 @@
             switchToScene(SceneKey.Lockscreen)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
 
             underTest.attemptDeviceEntry()
 
@@ -340,7 +362,7 @@
     @Test
     fun isBypassEnabled_disabledInRepository_false() =
         testScope.runTest {
-            utils.deviceEntryRepository.setBypassEnabled(false)
+            kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
             assertThat(underTest.isBypassEnabled.value).isFalse()
         }
 
@@ -348,8 +370,10 @@
     fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             assertThat(isUnlocked).isFalse()
 
             authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index c4ebbdc..6f62afc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -52,6 +53,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.verify
@@ -68,6 +71,8 @@
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
+    @Mock private lateinit var userTracker: UserTracker
+    @Captor private lateinit var updateCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
     private val mainDispatcher = StandardTestDispatcher()
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -93,6 +98,7 @@
                 testScope.backgroundScope,
                 systemClock,
                 facePropertyRepository,
+                userTracker,
             )
     }
 
@@ -553,4 +559,25 @@
 
             job.cancel()
         }
+
+    @Test
+    fun isEncryptedOrLockdown() =
+        testScope.runTest {
+            whenever(userTracker.userId).thenReturn(0)
+            whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(true)
+
+            // Default value for isEncryptedOrLockdown is true
+            val isEncryptedOrLockdown by collectLastValue(underTest.isEncryptedOrLockdown)
+            assertThat(isEncryptedOrLockdown).isTrue()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateCallbackCaptor.capture())
+            val updateCallback = updateCallbackCaptor.value
+
+            // Strong auth state updated
+            whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(false)
+            updateCallback.onStrongAuthStateChanged(0)
+
+            // Verify no longer encrypted or lockdown
+            assertThat(isEncryptedOrLockdown).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 8933d2c..2c3afb1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -26,12 +26,16 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -48,10 +52,10 @@
 @RunWith(AndroidJUnit4::class)
 class KeyguardInteractorTest : SysuiTestCase() {
 
-    private val testUtils = SceneTestUtils(this)
-    private val testScope = testUtils.testScope
-    private val repository = testUtils.keyguardRepository
-    private val sceneInteractor = testUtils.sceneInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val repository = kosmos.fakeKeyguardRepository
+    private val sceneInteractor = kosmos.sceneInteractor
     private val commandQueue = FakeCommandQueue()
     private val bouncerRepository = FakeKeyguardBouncerRepository()
     private val shadeRepository = FakeShadeRepository()
@@ -63,7 +67,7 @@
             repository = repository,
             commandQueue = commandQueue,
             powerInteractor = PowerInteractorFactory.create().powerInteractor,
-            sceneContainerFlags = testUtils.fakeSceneContainerFlags,
+            sceneContainerFlags = kosmos.fakeSceneContainerFlags,
             bouncerRepository = bouncerRepository,
             configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
             shadeRepository = shadeRepository,
@@ -183,7 +187,7 @@
     @Test
     fun animationDozingTransitions() =
         testScope.runTest {
-            testUtils.fakeSceneContainerFlags.enabled = true
+            kosmos.fakeSceneContainerFlags.enabled = true
             val isAnimate by collectLastValue(underTest.animateDozingTransitions)
 
             underTest.setAnimateDozingTransitions(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 04e90c8..aa15d0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -21,11 +21,19 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,9 +45,9 @@
 @RunWith(AndroidJUnit4::class)
 class LockscreenSceneViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
 
     private val underTest = createLockscreenSceneViewModel()
 
@@ -47,9 +55,11 @@
     fun upTransitionSceneKey_canSwipeToUnlock_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
@@ -59,8 +69,10 @@
     fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -69,7 +81,7 @@
     @Test
     fun leftTransitionSceneKey_communalIsEnabled_communal() =
         testScope.runTest {
-            utils.communalRepository.setIsCommunalEnabled(true)
+            kosmos.fakeCommunalRepository.setIsCommunalEnabled(true)
             val underTest = createLockscreenSceneViewModel()
 
             assertThat(underTest.leftDestinationSceneKey).isEqualTo(SceneKey.Communal)
@@ -78,7 +90,7 @@
     @Test
     fun leftTransitionSceneKey_communalIsDisabled_null() =
         testScope.runTest {
-            utils.communalRepository.setIsCommunalEnabled(false)
+            kosmos.fakeCommunalRepository.setIsCommunalEnabled(false)
             val underTest = createLockscreenSceneViewModel()
 
             assertThat(underTest.leftDestinationSceneKey).isNull()
@@ -87,13 +99,13 @@
     private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
         return LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            deviceEntryInteractor = utils.deviceEntryInteractor(),
-            communalInteractor = utils.communalInteractor(),
+            deviceEntryInteractor = kosmos.deviceEntryInteractor,
+            communalInteractor = kosmos.communalInteractor,
             longPress =
                 KeyguardLongPressViewModel(
                     interactor = mock(),
                 ),
-            notifications = utils.notificationsPlaceholderViewModel(),
+            notifications = kosmos.notificationsPlaceholderViewModel,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 6403ed1..be523b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -22,13 +22,15 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -36,6 +38,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
@@ -47,9 +50,9 @@
 @RunWith(AndroidJUnit4::class)
 class QuickSettingsSceneViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
     private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
@@ -90,7 +93,7 @@
             QuickSettingsSceneViewModel(
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
-                notifications = utils.notificationsPlaceholderViewModel(),
+                notifications = kosmos.notificationsPlaceholderViewModel,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index ecc2ef1..1cd764e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -27,22 +27,38 @@
 import com.android.internal.util.emergencyAffordanceManager
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.model.SysUiState
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -50,13 +66,17 @@
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -101,13 +121,13 @@
 @RunWith(AndroidJUnit4::class)
 class SceneFrameworkIntegrationTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
-    private val testScope = utils.testScope
-    private val sceneContainerConfig = utils.fakeSceneContainerConfig()
-    private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val deviceEntryInteractor = utils.deviceEntryInteractor()
-    private val communalInteractor = utils.communalInteractor()
+    private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    private val testScope = kosmos.testScope
+    private val sceneContainerConfig = kosmos.sceneContainerConfig
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val authenticationInteractor = kosmos.authenticationInteractor
+    private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+    private val communalInteractor = kosmos.communalInteractor
 
     private val transitionState =
         MutableStateFlow<ObservableTransitionState>(
@@ -116,11 +136,11 @@
     private val sceneContainerViewModel =
         SceneContainerViewModel(
                 sceneInteractor = sceneInteractor,
-                falsingInteractor = utils.falsingInteractor(),
+                falsingInteractor = kosmos.falsingInteractor,
             )
             .apply { setTransitionState(transitionState) }
 
-    private val bouncerInteractor = utils.bouncerInteractor()
+    private val bouncerInteractor = kosmos.bouncerInteractor
 
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
     private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
@@ -135,7 +155,7 @@
                 KeyguardLongPressViewModel(
                     interactor = mock(),
                 ),
-            notifications = utils.notificationsPlaceholderViewModel(),
+            notifications = kosmos.notificationsPlaceholderViewModel,
         )
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@@ -152,22 +172,21 @@
                     FakeMobileConnectionsRepository(),
                 ),
             constants = mock(),
-            utils.fakeFeatureFlags,
+            flags = kosmos.fakeFeatureFlagsClassic,
             scope = testScope.backgroundScope,
         )
 
     private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
     private lateinit var shadeSceneViewModel: ShadeSceneViewModel
 
-    private val keyguardInteractor = utils.keyguardInteractor()
-    private val powerInteractor = utils.powerInteractor()
+    private val keyguardInteractor = kosmos.keyguardInteractor
+    private val powerInteractor = kosmos.powerInteractor
 
     private var bouncerSceneJob: Job? = null
 
     private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { mock() })
 
     @Mock private lateinit var mediaDataManager: MediaDataManager
-    @Mock private lateinit var mediaHost: MediaHost
 
     private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
     private lateinit var telecomManager: TelecomManager
@@ -177,27 +196,27 @@
         MockitoAnnotations.initMocks(this)
 
         overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, true)
-        telecomManager = checkNotNull(utils.kosmos.telecomManager)
+        telecomManager = checkNotNull(kosmos.telecomManager)
         whenever(telecomManager.isInCall).thenReturn(false)
-        emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager
+        emergencyAffordanceManager = kosmos.emergencyAffordanceManager
         whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true)
 
-        utils.fakeFeatureFlags.apply {
+        kosmos.fakeFeatureFlagsClassic.apply {
             set(Flags.NEW_NETWORK_SLICE_UI, false)
             set(Flags.REFACTOR_GETCURRENTUSER, true)
         }
 
-        mobileConnectionsRepository = utils.mobileConnectionsRepository
+        mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
         mobileConnectionsRepository.isAnySimSecure.value = false
 
-        utils.telephonyRepository.apply {
+        kosmos.fakeTelephonyRepository.apply {
             setHasTelephonyRadio(true)
             setCallState(TelephonyManager.CALL_STATE_IDLE)
             setIsInCall(false)
         }
 
-        bouncerActionButtonInteractor = utils.bouncerActionButtonInteractor()
-        bouncerViewModel = utils.bouncerViewModel()
+        bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
+        bouncerViewModel = kosmos.bouncerViewModel
 
         shadeHeaderViewModel =
             ShadeHeaderViewModel(
@@ -215,12 +234,11 @@
                 deviceEntryInteractor = deviceEntryInteractor,
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
-                notifications = utils.notificationsPlaceholderViewModel(),
+                notifications = kosmos.notificationsPlaceholderViewModel,
                 mediaDataManager = mediaDataManager,
-                mediaHost = mediaHost,
             )
 
-        utils.deviceEntryRepository.setUnlocked(false)
+        kosmos.fakeDeviceEntryRepository.setUnlocked(false)
 
         val displayTracker = FakeDisplayTracker(context)
         val sysUiState = SysUiState(displayTracker)
@@ -230,15 +248,15 @@
                 sceneInteractor = sceneInteractor,
                 deviceEntryInteractor = deviceEntryInteractor,
                 keyguardInteractor = keyguardInteractor,
-                flags = utils.fakeSceneContainerFlags,
+                flags = kosmos.fakeSceneContainerFlags,
                 sysUiState = sysUiState,
                 displayId = displayTracker.defaultDisplayId,
                 sceneLogger = mock(),
-                falsingCollector = utils.falsingCollector(),
+                falsingCollector = kosmos.falsingCollector,
                 powerInteractor = powerInteractor,
                 bouncerInteractor = bouncerInteractor,
-                simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
-                authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() },
+                simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
+                authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
                 windowController = mock(),
             )
         startable.start()
@@ -534,15 +552,15 @@
         // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
         // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
         // is not an observable that can trigger a new evaluation.
-        utils.deviceEntryRepository.setLockscreenEnabled(enableLockscreen)
-        utils.authenticationRepository.setAuthenticationMethod(authMethod)
+        kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen)
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
         runCurrent()
     }
 
     /** Emulates a phone call in progress. */
     private fun TestScope.startPhoneCall() {
         whenever(telecomManager.isInCall).thenReturn(true)
-        utils.telephonyRepository.apply {
+        kosmos.fakeTelephonyRepository.apply {
             setHasTelephonyRadio(true)
             setIsInCall(true)
             setCallState(TelephonyManager.CALL_STATE_OFFHOOK)
@@ -651,7 +669,7 @@
             .that(authMethod.isSecure)
             .isTrue()
 
-        utils.deviceEntryRepository.setUnlocked(false)
+        kosmos.fakeDeviceEntryRepository.setUnlocked(false)
         runCurrent()
     }
 
@@ -665,7 +683,7 @@
         enterPin()
         // This repository state is not changed by the AuthInteractor, it relies on
         // KeyguardStateController.
-        utils.deviceEntryRepository.setUnlocked(true)
+        kosmos.fakeDeviceEntryRepository.setUnlocked(true)
         emulateUiSceneTransition(
             expectedVisible = false,
         )
@@ -721,7 +739,7 @@
         }
         pinBouncerViewModel.onAuthenticateButtonClicked()
         setAuthMethod(authMethodAfterSimUnlock)
-        utils.mobileConnectionsRepository.isAnySimSecure.value = false
+        kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
         runCurrent()
     }
 
@@ -768,7 +786,7 @@
 
     private fun TestScope.introduceLockedSim() {
         setAuthMethod(AuthenticationMethodModel.Sim)
-        utils.mobileConnectionsRepository.isAnySimSecure.value = true
+        kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
         runCurrent()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 339d026..b267720 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -22,11 +22,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,12 +42,12 @@
 @RunWith(AndroidJUnit4::class)
 class SceneContainerRepositoryTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    private val testScope = kosmos.testScope
 
     @Test
     fun allSceneKeys() {
-        val underTest = utils.fakeSceneContainerRepository()
+        val underTest = kosmos.sceneContainerRepository
         assertThat(underTest.allSceneKeys())
             .isEqualTo(
                 listOf(
@@ -61,7 +64,7 @@
     @Test
     fun desiredScene() =
         testScope.runTest {
-            val underTest = utils.fakeSceneContainerRepository()
+            val underTest = kosmos.sceneContainerRepository
             val currentScene by collectLastValue(underTest.desiredScene)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
@@ -71,15 +74,15 @@
 
     @Test(expected = IllegalStateException::class)
     fun setDesiredScene_noSuchSceneInContainer_throws() {
-        utils.kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
-        val underTest = utils.fakeSceneContainerRepository()
+        kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+        val underTest = kosmos.sceneContainerRepository
         underTest.setDesiredScene(SceneModel(SceneKey.Shade))
     }
 
     @Test
     fun isVisible() =
         testScope.runTest {
-            val underTest = utils.fakeSceneContainerRepository()
+            val underTest = kosmos.sceneContainerRepository
             val isVisible by collectLastValue(underTest.isVisible)
             assertThat(isVisible).isTrue()
 
@@ -93,19 +96,19 @@
     @Test
     fun transitionState_defaultsToIdle() =
         testScope.runTest {
-            val underTest = utils.fakeSceneContainerRepository()
+            val underTest = kosmos.sceneContainerRepository
             val transitionState by collectLastValue(underTest.transitionState)
 
             assertThat(transitionState)
                 .isEqualTo(
-                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                    ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
                 )
         }
 
     @Test
     fun transitionState_reflectsUpdates() =
         testScope.runTest {
-            val underTest = utils.fakeSceneContainerRepository()
+            val underTest = kosmos.sceneContainerRepository
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -134,7 +137,7 @@
             underTest.setTransitionState(null)
             assertThat(reflectedTransitionState)
                 .isEqualTo(
-                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                    ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
                 )
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 486f7ab..d159986 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -20,10 +20,16 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
@@ -36,20 +42,20 @@
 @RunWith(AndroidJUnit4::class)
 class SceneInteractorTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var underTest: SceneInteractor
 
     @Before
     fun setUp() {
-        utils.fakeSceneContainerFlags.enabled = true
-        underTest = utils.sceneInteractor()
+        kosmos.fakeSceneContainerFlags.enabled = true
+        underTest = kosmos.sceneInteractor
     }
 
     @Test
     fun allSceneKeys() {
-        assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys())
+        assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys)
     }
 
     @Test
@@ -75,7 +81,7 @@
     @Test
     fun transitionState() =
         testScope.runTest {
-            val underTest = utils.fakeSceneContainerRepository()
+            val underTest = kosmos.sceneContainerRepository
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -104,7 +110,7 @@
             underTest.setTransitionState(null)
             assertThat(reflectedTransitionState)
                 .isEqualTo(
-                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                    ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
                 )
         }
 
@@ -348,8 +354,8 @@
     @Test
     fun userInput() =
         testScope.runTest {
-            assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+            assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
             underTest.onUserInput()
-            assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+            assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 5fe4ca1..4afa5f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -25,19 +25,31 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.model.SysUiState
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -65,15 +77,15 @@
 
     @Mock private lateinit var windowController: NotificationShadeWindowController
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
-    private val sceneContainerFlags = utils.fakeSceneContainerFlags
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val bouncerInteractor = utils.bouncerInteractor()
-    private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository
-    private val deviceEntryInteractor = utils.deviceEntryInteractor()
-    private val keyguardInteractor = utils.keyguardInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val sceneContainerFlags = kosmos.fakeSceneContainerFlags
+    private val authenticationInteractor = kosmos.authenticationInteractor
+    private val bouncerInteractor = kosmos.bouncerInteractor
+    private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+    private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+    private val keyguardInteractor = kosmos.keyguardInteractor
     private val sysUiState: SysUiState = mock()
     private val falsingCollector: FalsingCollector = mock()
     private val powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -97,7 +109,7 @@
                 falsingCollector = falsingCollector,
                 powerInteractor = powerInteractor,
                 bouncerInteractor = bouncerInteractor,
-                simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+                simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
                 authenticationInteractor = dagger.Lazy { authenticationInteractor },
                 windowController = windowController,
             )
@@ -172,7 +184,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
             underTest.start()
 
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -188,7 +200,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
             underTest.start()
 
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -204,7 +216,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -222,7 +234,7 @@
 
             // Authenticate using a passive auth method like face auth while bypass is disabled.
             faceAuthRepository.isAuthenticated.value = true
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -244,7 +256,7 @@
             transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade)
             assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
 
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
@@ -263,7 +275,7 @@
 
             // Authenticate using a passive auth method like face auth while bypass is disabled.
             faceAuthRepository.isAuthenticated.value = true
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -373,7 +385,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
             powerInteractor.setAwakeForTest()
             runCurrent()
@@ -463,11 +475,11 @@
             runCurrent()
             verify(falsingCollector).setShowingAod(false)
 
-            utils.keyguardRepository.setIsDozing(true)
+            kosmos.fakeKeyguardRepository.setIsDozing(true)
             runCurrent()
             verify(falsingCollector).setShowingAod(true)
 
-            utils.keyguardRepository.setIsDozing(false)
+            kosmos.fakeKeyguardRepository.setIsDozing(false)
             runCurrent()
             verify(falsingCollector, times(2)).setShowingAod(false)
         }
@@ -493,7 +505,7 @@
     @Test
     fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
         testScope.runTest {
-            utils.keyguardRepository.setAodAvailable(false)
+            kosmos.fakeKeyguardRepository.setAodAvailable(false)
             runCurrent()
             prepareState(
                 initialSceneKey = SceneKey.Lockscreen,
@@ -541,7 +553,7 @@
     @Test
     fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
         testScope.runTest {
-            utils.keyguardRepository.setAodAvailable(true)
+            kosmos.fakeKeyguardRepository.setAodAvailable(true)
             runCurrent()
             prepareState(
                 initialSceneKey = SceneKey.Lockscreen,
@@ -619,7 +631,7 @@
             underTest.start()
             runCurrent()
 
-            utils.mobileConnectionsRepository.isAnySimSecure.value = true
+            kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
             runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -628,7 +640,7 @@
     @Test
     fun switchesToLockscreen_whenSimBecomesUnlocked() =
         testScope.runTest {
-            utils.mobileConnectionsRepository.isAnySimSecure.value = true
+            kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
 
             prepareState(
@@ -638,7 +650,7 @@
             )
             underTest.start()
             runCurrent()
-            utils.mobileConnectionsRepository.isAnySimSecure.value = false
+            kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
             runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
@@ -647,7 +659,7 @@
     @Test
     fun switchesToGone_whenSimBecomesUnlocked_ifDeviceUnlockedAndLockscreenDisabled() =
         testScope.runTest {
-            utils.mobileConnectionsRepository.isAnySimSecure.value = true
+            kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
 
             prepareState(
@@ -658,7 +670,7 @@
             )
             underTest.start()
             runCurrent()
-            utils.mobileConnectionsRepository.isAnySimSecure.value = false
+            kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
             runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
@@ -730,8 +742,8 @@
             }
         }
         sceneContainerFlags.enabled = true
-        utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked)
-        utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled)
+        kosmos.fakeDeviceEntryRepository.setUnlocked(isDeviceUnlocked)
+        kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled)
         val transitionStateFlow =
             MutableStateFlow<ObservableTransitionState>(
                 ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -743,8 +755,8 @@
             sceneInteractor.onSceneChanged(SceneModel(it), "reason")
         }
         authenticationMethod?.let {
-            utils.authenticationRepository.setAuthenticationMethod(authenticationMethod)
-            utils.deviceEntryRepository.setLockscreenEnabled(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(
                 isLockscreenEnabled = isLockscreenEnabled
             )
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 3a4ee64..ede453d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -21,10 +21,14 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
@@ -36,17 +40,17 @@
 @RunWith(AndroidJUnit4::class)
 class SceneContainerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val interactor = utils.sceneInteractor()
+    private val kosmos = testKosmos()
+    private val interactor = kosmos.sceneInteractor
     private lateinit var underTest: SceneContainerViewModel
 
     @Before
     fun setUp() {
-        utils.fakeSceneContainerFlags.enabled = true
+        kosmos.fakeSceneContainerFlags.enabled = true
         underTest =
             SceneContainerViewModel(
                 sceneInteractor = interactor,
-                falsingInteractor = utils.falsingInteractor(),
+                falsingInteractor = kosmos.falsingInteractor,
             )
     }
 
@@ -64,7 +68,7 @@
 
     @Test
     fun allSceneKeys() {
-        assertThat(underTest.allSceneKeys).isEqualTo(utils.fakeSceneKeys())
+        assertThat(underTest.allSceneKeys).isEqualTo(kosmos.sceneKeys)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 77ddf15..5174502 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -7,7 +7,8 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -18,6 +19,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -31,9 +33,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ShadeHeaderViewModelTest : SysuiTestCase() {
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index a8133a3a..e980165 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -19,16 +19,20 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -36,6 +40,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -53,10 +58,10 @@
 @RunWith(AndroidJUnit4::class)
 class ShadeSceneViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
-    private val deviceEntryInteractor = utils.deviceEntryInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val deviceEntryInteractor = kosmos.deviceEntryInteractor
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -84,7 +89,6 @@
     private lateinit var underTest: ShadeSceneViewModel
 
     @Mock private lateinit var mediaDataManager: MediaDataManager
-    @Mock private lateinit var mediaHost: MediaHost
 
     @Before
     fun setUp() {
@@ -105,9 +109,8 @@
                 deviceEntryInteractor = deviceEntryInteractor,
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
-                notifications = utils.notificationsPlaceholderViewModel(),
+                notifications = kosmos.notificationsPlaceholderViewModel,
                 mediaDataManager = mediaDataManager,
-                mediaHost = mediaHost,
             )
     }
 
@@ -115,8 +118,10 @@
     fun upTransitionSceneKey_deviceLocked_lockScreen() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -125,8 +130,10 @@
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -135,8 +142,10 @@
     fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
 
@@ -147,8 +156,10 @@
     fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
             sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
 
@@ -159,8 +170,10 @@
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -172,8 +185,10 @@
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
deleted file mode 100644
index 2cbcc5a..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class GroupMembershipManagerTest : SysuiTestCase() {
-    private var underTest = GroupMembershipManagerImpl()
-
-    @Test
-    fun isChildInGroup_topLevel() {
-        val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
-        assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse()
-    }
-
-    @Test
-    fun isChildInGroup_noParent() {
-        val noParentEntry = NotificationEntryBuilder().setParent(null).build()
-        assertThat(underTest.isChildInGroup(noParentEntry)).isFalse()
-    }
-
-    @Test
-    fun isChildInGroup_summary() {
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(underTest.isChildInGroup(summary)).isFalse()
-    }
-
-    @Test
-    fun isGroupSummary_topLevelEntry() {
-        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
-        assertThat(underTest.isGroupSummary(entry)).isFalse()
-    }
-
-    @Test
-    fun isGroupSummary_summary() {
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(underTest.isGroupSummary(summary)).isTrue()
-    }
-
-    @Test
-    fun isGroupSummary_child() {
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
-        assertThat(underTest.isGroupSummary(entry)).isFalse()
-    }
-
-    @Test
-    fun getGroupSummary_topLevelEntry() {
-        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
-        assertThat(underTest.getGroupSummary(entry)).isNull()
-    }
-
-    @Test
-    fun getGroupSummary_summary() {
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(underTest.getGroupSummary(summary)).isEqualTo(summary)
-    }
-
-    @Test
-    fun getGroupSummary_child() {
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
-        assertThat(underTest.getGroupSummary(entry)).isEqualTo(summary)
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
index 262795f..8e8e510 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -24,12 +24,13 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -39,7 +40,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class TelephonyRepositoryImplTest : SysuiTestCase() {
@@ -47,8 +47,8 @@
     @Mock private lateinit var manager: TelephonyListenerManager
     @Mock private lateinit var telecomManager: TelecomManager
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var underTest: TelephonyRepositoryImpl
 
@@ -61,7 +61,7 @@
             TelephonyRepositoryImpl(
                 applicationScope = testScope.backgroundScope,
                 applicationContext = context,
-                backgroundDispatcher = utils.testDispatcher,
+                backgroundDispatcher = kosmos.testDispatcher,
                 manager = manager,
                 telecomManager = telecomManager,
             )
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index 23fbb12..10f7113 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -20,6 +20,13 @@
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
+    <ImageView
+        android:id="@+id/logo"
+        android:layout_width="@dimen/biometric_auth_icon_size"
+        android:layout_height="@dimen/biometric_auth_icon_size"
+        android:layout_gravity="center"
+        android:scaleType="fitXY"/>
+
     <TextView
         android:id="@+id/title"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 0534c6e..a0f916c 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -17,6 +17,7 @@
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/root"
     style="@style/Widget.SliceView.Panel"
     android:layout_width="wrap_content"
@@ -53,17 +54,40 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" />
 
+    <View
+        android:id="@+id/bluetooth_tile_dialog_progress_background"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintEnd_toEndOf="@id/bluetooth_tile_dialog_progress_animation"
+        app:layout_constraintStart_toStartOf="@id/bluetooth_tile_dialog_progress_animation"
+        app:layout_constraintTop_toTopOf="@id/bluetooth_tile_dialog_progress_animation"
+        app:layout_constraintBottom_toBottomOf="@id/bluetooth_tile_dialog_progress_animation"
+        android:background="?androidprv:attr/colorSurfaceVariant" />
+
+    <ProgressBar
+        android:id="@+id/bluetooth_tile_dialog_progress_animation"
+        android:layout_width="152dp"
+        android:layout_height="4dp"
+        android:layout_marginTop="16dp"
+        style="@style/TrimmedHorizontalProgressBar"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle"
+        android:visibility="invisible"
+        android:indeterminate="true" />
+
     <androidx.core.widget.NestedScrollView
         android:id="@+id/scroll_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="21dp"
+        android:minHeight="145dp"
         android:fillViewport="true"
         app:layout_constrainedHeight="true"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle">
+        app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_progress_animation">
 
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/scroll_layout"
@@ -81,7 +105,7 @@
                 android:paddingStart="36dp"
                 android:text="@string/turn_on_bluetooth"
                 android:clickable="false"
-                android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+                android:textAppearance="@style/TextAppearance.Dialog.Title"
                 android:textSize="16sp"
                 app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
                 app:layout_constraintStart_toStartOf="parent"
@@ -90,8 +114,7 @@
             <Switch
                 android:id="@+id/bluetooth_toggle"
                 android:layout_width="wrap_content"
-                android:layout_height="48dp"
-                android:paddingTop="10dp"
+                android:layout_height="64dp"
                 android:gravity="start|center_vertical"
                 android:paddingEnd="40dp"
                 android:contentDescription="@string/turn_on_bluetooth"
@@ -107,7 +130,6 @@
                 android:id="@+id/device_list"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="20dp"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 85b6e8d..5db9eee 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -50,7 +50,10 @@
         app:layout_constraintStart_toStartOf="@id/album_art"
         app:layout_constraintEnd_toEndOf="@id/album_art"
         app:layout_constraintTop_toTopOf="@id/album_art"
-        app:layout_constraintBottom_toBottomOf="@id/album_art" />
+        app:layout_constraintBottom_toBottomOf="@id/album_art"
+        android:clipToOutline="true"
+        android:background="@drawable/qs_media_outline_layout_bg"
+        />
 
     <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
         android:id="@+id/turbulence_noise_view"
@@ -59,7 +62,10 @@
         app:layout_constraintStart_toStartOf="@id/album_art"
         app:layout_constraintEnd_toEndOf="@id/album_art"
         app:layout_constraintTop_toTopOf="@id/album_art"
-        app:layout_constraintBottom_toBottomOf="@id/album_art" />
+        app:layout_constraintBottom_toBottomOf="@id/album_art"
+        android:clipToOutline="true"
+        android:background="@drawable/qs_media_outline_layout_bg"
+        />
 
     <!-- Guideline for output switcher -->
     <androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 73874a0..b3f32a2 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -19,7 +19,7 @@
 <com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/qs_footer"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="@dimen/qs_footer_height"
     android:layout_marginStart="@dimen/qs_footer_margin"
     android:layout_marginEnd="@dimen/qs_footer_margin"
     android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
@@ -31,7 +31,7 @@
 
         <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="@dimen/qs_footer_height"
+            android:layout_height="match_parent"
             android:layout_gravity="center_vertical">
 
             <TextView
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
index be34a48..0955f67 100644
--- a/packages/SystemUI/res/values-sw720dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -24,9 +24,6 @@
     <!-- The maximum number of tiles in the QuickQSPanel -->
     <integer name="quick_qs_panel_max_tiles">6</integer>
 
-    <!-- Whether to use the split 2-column notification shade -->
-    <bool name="config_use_split_notification_shade">true</bool>
-
     <!-- The number of columns in the QuickSettings -->
     <integer name="quick_settings_num_columns">3</integer>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 798fc06b..ee2a1ce 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -897,6 +897,10 @@
     <dimen name="communal_tutorial_indicator_padding">24dp</dimen>
     <dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen>
 
+    <!-- Size of the maximum radius for the enforced rounded rectangles on communal hub.
+        Keep it the same as in Launcher-->
+    <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
+
     <!-- The width/height of the unlock icon view on keyguard. -->
     <dimen name="keyguard_lock_height">42dp</dimen>
     <dimen name="keyguard_lock_padding">20dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index ab23564..57e308f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -399,7 +399,8 @@
                     config.mPromptInfo,
                     config.mUserId,
                     config.mOperationId,
-                    new BiometricModalities(fpProps, faceProps));
+                    new BiometricModalities(fpProps, faceProps),
+                    config.mOpPackageName);
 
             final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
                     R.layout.biometric_prompt_layout, null, false);
@@ -470,7 +471,8 @@
         mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
 
         mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication(
-                mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId);
+                mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId,
+                mConfig.mOpPackageName);
         final CredentialViewModel vm = mCredentialViewModelProvider.get();
         vm.setAnimateContents(animateContents);
         ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index 86802a5b..02eae9ced 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -148,6 +148,12 @@
                     || child.getId() == R.id.customized_view_container) {
                 //skip description view and compute later
                 continue;
+            } else if (child.getId() == R.id.logo) {
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
+                                MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+                                MeasureSpec.EXACTLY));
             } else {
                 child.measure(
                         MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b35fbbc..ad7bb0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -55,6 +55,9 @@
     /** The kind of credential to use (biometric, pin, pattern, etc.). */
     val kind: StateFlow<PromptKind>
 
+    /** The package name that the prompt is called from. */
+    val opPackageName: StateFlow<String?>
+
     /**
      * If explicit confirmation is required.
      *
@@ -68,6 +71,7 @@
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
+        opPackageName: String,
     )
 
     /** Unset the prompt info. */
@@ -108,6 +112,9 @@
     private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
     override val kind = _kind.asStateFlow()
 
+    private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
+    override val opPackageName = _opPackageName.asStateFlow()
+
     private val _faceSettings =
         _userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
     private val _faceSettingAlwaysRequireConfirmation =
@@ -127,11 +134,13 @@
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
+        opPackageName: String,
     ) {
         _kind.value = kind
         _userId.value = userId
         _challenge.value = gatekeeperChallenge
         _promptInfo.value = promptInfo
+        _opPackageName.value = opPackageName
     }
 
     override fun unsetPrompt() {
@@ -139,6 +148,7 @@
         _userId.value = null
         _challenge.value = null
         _kind.value = PromptKind.Biometric()
+        _opPackageName.value = null
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index ac4b717..359e2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -115,12 +115,14 @@
         @Utils.CredentialType kind: Int,
         userId: Int,
         challenge: Long,
+        opPackageName: String,
     ) {
         biometricPromptRepository.setPrompt(
             promptInfo,
             userId,
             challenge,
-            kind.asBiometricPromptCredential()
+            kind.asBiometricPromptCredential(),
+            opPackageName,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 65a2c0a..b3f9574 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -76,6 +76,7 @@
         userId: Int,
         challenge: Long,
         modalities: BiometricModalities,
+        opPackageName: String,
     )
 
     /** Use credential-based authentication instead of biometrics. */
@@ -84,6 +85,7 @@
         @Utils.CredentialType kind: Int,
         userId: Int,
         challenge: Long,
+        opPackageName: String,
     )
 
     /** Unset the current authentication request. */
@@ -104,9 +106,12 @@
             promptRepository.promptInfo,
             promptRepository.challenge,
             promptRepository.userId,
-            promptRepository.kind
-        ) { promptInfo, challenge, userId, kind ->
-            if (promptInfo == null || userId == null || challenge == null) {
+            promptRepository.kind,
+            promptRepository.opPackageName,
+        ) { promptInfo, challenge, userId, kind, opPackageName ->
+            if (
+                promptInfo == null || userId == null || challenge == null || opPackageName == null
+            ) {
                 return@combine null
             }
 
@@ -117,6 +122,7 @@
                         userInfo = BiometricUserInfo(userId = userId),
                         operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge),
                         modalities = kind.activeModalities,
+                        opPackageName = opPackageName,
                     )
                 else -> null
             }
@@ -152,13 +158,15 @@
         promptInfo: PromptInfo,
         userId: Int,
         challenge: Long,
-        modalities: BiometricModalities
+        modalities: BiometricModalities,
+        opPackageName: String,
     ) {
         promptRepository.setPrompt(
             promptInfo = promptInfo,
             userId = userId,
             gatekeeperChallenge = challenge,
             kind = PromptKind.Biometric(modalities),
+            opPackageName = opPackageName,
         )
     }
 
@@ -167,12 +175,14 @@
         @Utils.CredentialType kind: Int,
         userId: Int,
         challenge: Long,
+        opPackageName: String,
     ) {
         promptRepository.setPrompt(
             promptInfo = promptInfo,
             userId = userId,
             gatekeeperChallenge = challenge,
             kind = kind.asBiometricPromptCredential(),
+            opPackageName = opPackageName,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 4377937..c17c8dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.biometrics.domain.model
 
+import android.graphics.Bitmap
 import android.hardware.biometrics.PromptContentView
 import android.hardware.biometrics.PromptInfo
 import com.android.systemui.biometrics.shared.model.BiometricModalities
@@ -26,6 +27,7 @@
         userInfo: BiometricUserInfo,
         operationInfo: BiometricOperationInfo,
         val modalities: BiometricModalities,
+        val opPackageName: String,
     ) :
         BiometricPromptRequest(
             title = info.title?.toString() ?: "",
@@ -36,6 +38,8 @@
             showEmergencyCallButton = info.isShowEmergencyCallButton
         ) {
         val contentView: PromptContentView? = info.contentView
+        val logoRes: Int = info.logoRes
+        val logoBitmap: Bitmap? = info.logoBitmap
         val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index 60b454e..b450896 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -115,6 +115,12 @@
                                 MeasureSpec.EXACTLY),
                         MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height,
                                 MeasureSpec.EXACTLY));
+            } else if (child.getId() == R.id.logo) {
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
+                                MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+                                MeasureSpec.EXACTLY));
             } else if (child.getId() == R.id.biometric_icon) {
                 child.measure(
                         MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 04dc7a8..285ab4a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -31,6 +31,7 @@
 import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
 import android.view.accessibility.AccessibilityManager
 import android.widget.Button
+import android.widget.ImageView
 import android.widget.ScrollView
 import android.widget.TextView
 import androidx.lifecycle.DefaultLifecycleObserver
@@ -92,6 +93,7 @@
         val textColorHint =
             view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
 
+        val logoView = view.requireViewById<ImageView>(R.id.logo)
         val titleView = view.requireViewById<TextView>(R.id.title)
         val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
         val descriptionView = view.requireViewById<TextView>(R.id.description)
@@ -99,6 +101,8 @@
             view.requireViewById<ScrollView>(R.id.customized_view_container)
 
         // set selected to enable marquee unless a screen reader is enabled
+        logoView.isSelected =
+            !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         titleView.isSelected =
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         subtitleView.isSelected =
@@ -152,6 +156,7 @@
                 }
             }
 
+            logoView.setImageDrawable(viewModel.logo.first())
             titleView.text = viewModel.title.first()
             subtitleView.text = viewModel.subtitle.first()
             descriptionView.text = viewModel.description.first()
@@ -183,6 +188,7 @@
                     viewModel = viewModel,
                     viewsToHideWhenSmall =
                         listOf(
+                            logoView,
                             titleView,
                             subtitleView,
                             descriptionView,
@@ -190,6 +196,7 @@
                         ),
                     viewsToFadeInOnSizeChange =
                         listOf(
+                            logoView,
                             titleView,
                             subtitleView,
                             descriptionView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index c3bbaed..d5695f3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -25,6 +25,7 @@
 import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
 import android.widget.TextView
 import androidx.core.animation.addListener
 import androidx.core.view.doOnLayout
@@ -234,7 +235,13 @@
 
 private fun View.showContentOrHide(forceHide: Boolean = false) {
     val isTextViewWithBlankText = this is TextView && this.text.isBlank()
-    visibility = if (forceHide || isTextViewWithBlankText) View.GONE else View.VISIBLE
+    val isImageViewWithoutImage = this is ImageView && this.drawable == null
+    visibility =
+        if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
+            View.GONE
+        } else {
+            View.VISIBLE
+        }
 }
 
 private fun View.asVerticalAnimator(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 1c78928..dca0338 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import android.graphics.Rect
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
 import android.hardware.biometrics.BiometricPrompt
 import android.hardware.biometrics.PromptContentView
 import android.util.Log
@@ -233,6 +235,19 @@
             }
         }
 
+    /** Logo for the prompt. */
+    val logo: Flow<Drawable?> =
+        promptSelectorInteractor.prompt
+            .map {
+                when {
+                    it == null -> null
+                    it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
+                    it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
+                    else -> context.packageManager.getApplicationIcon(it.opPackageName)
+                }
+            }
+            .distinctUntilChanged()
+
     /** Title for the prompt. */
     val title: Flow<String> =
         promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
index 9a9b0e2..046aaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.communal.data.repository
 
 import android.provider.Settings
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
 import android.provider.Settings.Secure.HubModeTutorialState
 import com.android.systemui.dagger.SysUISingleton
@@ -24,7 +25,6 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -35,6 +35,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
@@ -62,14 +63,19 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
-    userRepository: UserRepository,
+    private val userRepository: UserRepository,
     private val secureSettings: SecureSettings,
-    private val userTracker: UserTracker,
     @CommunalLog logBuffer: LogBuffer,
 ) : CommunalTutorialRepository {
 
     companion object {
         private const val TAG = "CommunalTutorialRepository"
+
+        const val MIN_TUTORIAL_VERSION = HUB_MODE_TUTORIAL_COMPLETED
+
+        // A version number which ensures that users, regardless of their completion of previous
+        // versions, see the updated tutorial when this number is bumped.
+        const val CURRENT_TUTORIAL_VERSION = MIN_TUTORIAL_VERSION + 1
     }
 
     private data class SettingsState(
@@ -80,7 +86,7 @@
 
     private val settingsState: Flow<SettingsState> =
         userRepository.selectedUserInfo
-            .flatMapLatest { observeSettings() }
+            .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
             .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
 
     /** Emits the state of tutorial state in settings */
@@ -91,31 +97,37 @@
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = HUB_MODE_TUTORIAL_NOT_STARTED
+                initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
             )
 
-    private fun observeSettings(): Flow<SettingsState> =
+    private fun observeSettings(userId: Int): Flow<SettingsState> =
         secureSettings
             .observerFlow(
-                userId = userTracker.userId,
-                names =
-                    arrayOf(
-                        Settings.Secure.HUB_MODE_TUTORIAL_STATE,
-                    )
+                userId = userId,
+                names = arrayOf(Settings.Secure.HUB_MODE_TUTORIAL_STATE),
             )
             // Force an update
             .onStart { emit(Unit) }
-            .map { readFromSettings() }
+            .map { readFromSettings(userId) }
 
-    private suspend fun readFromSettings(): SettingsState =
+    private suspend fun readFromSettings(userId: Int): SettingsState =
         withContext(backgroundDispatcher) {
-            val userId = userTracker.userId
-            val hubModeTutorialState =
+            var hubModeTutorialState =
                 secureSettings.getIntForUser(
                     Settings.Secure.HUB_MODE_TUTORIAL_STATE,
                     HUB_MODE_TUTORIAL_NOT_STARTED,
                     userId,
                 )
+
+            if (hubModeTutorialState >= CURRENT_TUTORIAL_VERSION) {
+                // Tutorial is considered "completed" if the user has completed the current or a
+                // newer version.
+                hubModeTutorialState = HUB_MODE_TUTORIAL_COMPLETED
+            } else if (hubModeTutorialState >= MIN_TUTORIAL_VERSION) {
+                // Tutorial is considered "not started" if the user completed a version older than
+                // the current.
+                hubModeTutorialState = HUB_MODE_TUTORIAL_NOT_STARTED
+            }
             val settingsState = SettingsState(hubModeTutorialState)
             logger.d({ "Communal tutorial state for user $int1 in settings: $str1" }) {
                 int1 = userId
@@ -127,18 +139,40 @@
 
     override suspend fun setTutorialState(state: Int): Unit =
         withContext(backgroundDispatcher) {
-            val userId = userTracker.userId
+            val userId = userRepository.getSelectedUserInfo().id
             if (tutorialSettingState.value == state) {
                 return@withContext
             }
+            val newState =
+                if (state == HUB_MODE_TUTORIAL_COMPLETED) CURRENT_TUTORIAL_VERSION else state
             logger.d({ "Update communal tutorial state to $int1 for user $int2" }) {
-                int1 = state
+                int1 = newState
                 int2 = userId
             }
             secureSettings.putIntForUser(
                 Settings.Secure.HUB_MODE_TUTORIAL_STATE,
-                state,
+                newState,
                 userId,
             )
         }
 }
+
+// TODO(b/320769333): delete me and use the real repo above when tutorial is ready.
+@SysUISingleton
+class CommunalTutorialDisabledRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+) : CommunalTutorialRepository {
+    override val tutorialSettingState: StateFlow<Int> =
+        emptyFlow<Int>()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = HUB_MODE_TUTORIAL_COMPLETED,
+            )
+
+    override suspend fun setTutorialState(state: Int) {
+        // Do nothing
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
index 69b0a27..b4f838a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
@@ -22,6 +22,9 @@
 
 @Module
 interface CommunalTutorialRepositoryModule {
+    // TODO(b/320769333): use [CommunalTutorialRepositoryImpl] when tutorial is ready.
     @Binds
-    fun communalTutorialRepository(impl: CommunalTutorialRepositoryImpl): CommunalTutorialRepository
+    fun communalTutorialRepository(
+        impl: CommunalTutorialDisabledRepositoryImpl
+    ): CommunalTutorialRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e6816e9..1c362e9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -16,26 +16,21 @@
 
 package com.android.systemui.communal.data.repository
 
-import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
 import android.content.ComponentName
-import android.content.Intent
-import android.content.IntentFilter
-import android.os.UserManager
 import androidx.annotation.WorkerThread
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.data.db.CommunalItemRank
 import com.android.systemui.communal.data.db.CommunalWidgetDao
 import com.android.systemui.communal.data.db.CommunalWidgetItem
 import com.android.systemui.communal.shared.CommunalWidgetHost
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.settings.UserTracker
 import java.util.Optional
 import javax.inject.Inject
 import kotlin.coroutines.cancellation.CancellationException
@@ -43,16 +38,12 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
 
 /** Encapsulates the state of widgets for communal mode. */
 interface CommunalWidgetRepository {
@@ -75,23 +66,21 @@
      * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
      */
     fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
+
+    /** Update whether the app widget host should be active. */
+    fun updateAppWidgetHostActive(active: Boolean)
 }
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalWidgetRepositoryImpl
 @Inject
 constructor(
     private val appWidgetManager: Optional<AppWidgetManager>,
-    private val appWidgetHost: AppWidgetHost,
+    private val appWidgetHost: CommunalAppWidgetHost,
     @Application private val applicationScope: CoroutineScope,
     @Background private val bgDispatcher: CoroutineDispatcher,
-    broadcastDispatcher: BroadcastDispatcher,
-    communalRepository: CommunalRepository,
     private val communalWidgetHost: CommunalWidgetHost,
     private val communalWidgetDao: CommunalWidgetDao,
-    private val userManager: UserManager,
-    private val userTracker: UserTracker,
     @CommunalLog logBuffer: LogBuffer,
 ) : CommunalWidgetRepository {
     companion object {
@@ -100,40 +89,22 @@
 
     private val logger = Logger(logBuffer, TAG)
 
-    // Whether the [AppWidgetHost] is listening for updates.
-    private var isHostListening = false
-
-    private suspend fun isUserUnlockingOrUnlocked(): Boolean =
-        withContext(bgDispatcher) { userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) }
-
-    private val isUserUnlocked: Flow<Boolean> =
-        flowOf(communalRepository.isCommunalEnabled)
-            .flatMapLatest { enabled ->
-                if (enabled) {
-                    broadcastDispatcher
-                        .broadcastFlow(
-                            filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
-                            user = userTracker.userHandle
-                        )
-                        .onStart { emit(Unit) }
-                        .mapLatest { isUserUnlockingOrUnlocked() }
-                } else {
-                    emptyFlow()
-                }
-            }
-            .distinctUntilChanged()
-
-    private val isHostActive: Flow<Boolean> =
-        isUserUnlocked.map {
-            if (it) {
-                startListening()
-                true
-            } else {
-                stopListening()
-                false
-            }
+    override fun updateAppWidgetHostActive(active: Boolean) {
+        if (active == isHostActive.value) {
+            return
         }
 
+        if (active) {
+            appWidgetHost.startListening()
+        } else {
+            appWidgetHost.stopListening()
+        }
+        isHostActive.value = active
+    }
+
+    private val isHostActive = MutableStateFlow(false)
+
+    @OptIn(ExperimentalCoroutinesApi::class)
     override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
         isHostActive.flatMapLatest { isHostActive ->
             if (!isHostActive || !appWidgetManager.isPresent) {
@@ -218,22 +189,4 @@
             priority = entry.key.rank,
         )
     }
-
-    private fun startListening() {
-        if (isHostListening) {
-            return
-        }
-
-        appWidgetHost.startListening()
-        isHostListening = true
-    }
-
-    private fun stopListening() {
-        if (!isHostListening) {
-            return
-        }
-
-        appWidgetHost.stopListening()
-        isHostListening = false
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index d0d9e3f..52f42c1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -17,11 +17,11 @@
 
 package com.android.systemui.communal.data.repository
 
-import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
 import android.content.Context
 import android.content.res.Resources
 import com.android.systemui.communal.shared.CommunalWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -48,15 +48,15 @@
 
         @SysUISingleton
         @Provides
-        fun provideAppWidgetHost(@Application context: Context): AppWidgetHost {
-            return AppWidgetHost(context, APP_WIDGET_HOST_ID)
+        fun provideCommunalAppWidgetHost(@Application context: Context): CommunalAppWidgetHost {
+            return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID)
         }
 
         @SysUISingleton
         @Provides
         fun provideCommunalWidgetHost(
             appWidgetManager: Optional<AppWidgetManager>,
-            appWidgetHost: AppWidgetHost,
+            appWidgetHost: CommunalAppWidgetHost,
             @CommunalLog logBuffer: LogBuffer,
         ): CommunalWidgetHost {
             return CommunalWidgetHost(appWidgetManager, appWidgetHost, logBuffer)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index aa9a426..71523b9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.communal.domain.interactor
 
 import android.app.smartspace.SmartspaceTarget
-import android.appwidget.AppWidgetHost
 import android.content.ComponentName
 import com.android.systemui.communal.data.repository.CommunalMediaRepository
 import com.android.systemui.communal.data.repository.CommunalPrefsRepository
@@ -30,31 +29,40 @@
 import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates business-logic related to communal mode. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalInteractor
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     private val communalRepository: CommunalRepository,
     private val widgetRepository: CommunalWidgetRepository,
     private val communalPrefsRepository: CommunalPrefsRepository,
     mediaRepository: CommunalMediaRepository,
     smartspaceRepository: SmartspaceRepository,
     keyguardInteractor: KeyguardInteractor,
-    private val appWidgetHost: AppWidgetHost,
+    private val appWidgetHost: CommunalAppWidgetHost,
     private val editWidgetsActivityStarter: EditWidgetsActivityStarter
 ) {
 
@@ -62,6 +70,20 @@
     val isCommunalEnabled: Boolean
         get() = communalRepository.isCommunalEnabled
 
+    /** Whether communal features are enabled and available. */
+    val isCommunalAvailable: StateFlow<Boolean> =
+        flowOf(isCommunalEnabled)
+            .flatMapLatest { enabled ->
+                if (enabled) keyguardInteractor.isEncryptedOrLockdown.map { !it } else flowOf(false)
+            }
+            .distinctUntilChanged()
+            .onEach { available -> widgetRepository.updateAppWidgetHostActive(available) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
     /**
      * Target scene as requested by the underlying [SceneTransitionLayout] or through
      * [onSceneChanged].
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 46f957f..0d52afd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.communal.domain.model
 
-import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetProviderInfo
 import android.widget.RemoteViews
 import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import java.util.UUID
 
 /** Encapsulates data for a communal content. */
@@ -44,7 +44,7 @@
     class Widget(
         val appWidgetId: Int,
         val providerInfo: AppWidgetProviderInfo,
-        val appWidgetHost: AppWidgetHost,
+        val appWidgetHost: CommunalAppWidgetHost,
     ) : CommunalContentModel {
         override val key = KEY.widget(appWidgetId)
         // Widget size is always half.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
index 41f9cb4..7fe37cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.communal.shared
 
-import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
 import android.content.ComponentName
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
@@ -35,7 +35,7 @@
 @Inject
 constructor(
     private val appWidgetManager: Optional<AppWidgetManager>,
-    private val appWidgetHost: AppWidgetHost,
+    private val appWidgetHost: CommunalAppWidgetHost,
     @CommunalLog logBuffer: LogBuffer,
 ) {
     companion object {
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 4da348e..73bb0b0 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
@@ -33,6 +33,8 @@
     private val communalInteractor: CommunalInteractor,
     val mediaHost: MediaHost,
 ) {
+    val isCommunalAvailable: StateFlow<Boolean> = communalInteractor.isCommunalAvailable
+
     val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
 
     val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index fcad45f..317dd40 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -20,7 +20,6 @@
 import android.app.Activity.RESULT_CANCELED
 import android.app.Activity.RESULT_OK
 import android.app.ActivityOptions
-import android.appwidget.AppWidgetHost
 import android.content.ActivityNotFoundException
 import android.content.ComponentName
 import android.widget.RemoteViews
@@ -28,6 +27,7 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.log.CommunalUiEvent
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.dagger.MediaModule
@@ -48,7 +48,7 @@
 @Inject
 constructor(
     private val communalInteractor: CommunalInteractor,
-    private val appWidgetHost: AppWidgetHost,
+    private val appWidgetHost: CommunalAppWidgetHost,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     private val uiEventLogger: UiEventLogger,
 ) : BaseCommunalViewModel(communalInteractor, mediaHost) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
new file mode 100644
index 0000000..003c9d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Context
+
+/** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
+class CommunalAppWidgetHost(context: Context, hostId: Int) : AppWidgetHost(context, hostId) {
+    override fun onCreateView(
+        context: Context,
+        appWidgetId: Int,
+        appWidget: AppWidgetProviderInfo?
+    ): AppWidgetHostView {
+        return CommunalAppWidgetHostView(context)
+    }
+
+    /**
+     * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as
+     * `createView`. The only difference is that the returned value will be casted to
+     * [CommunalAppWidgetHostView].
+     */
+    fun createViewForCommunal(
+        context: Context?,
+        appWidgetId: Int,
+        appWidget: AppWidgetProviderInfo?
+    ): CommunalAppWidgetHostView {
+        // `createView` internally calls `onCreateView` to create the view. We cannot override
+        // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView`
+        return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
new file mode 100644
index 0000000..2b7d823
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.appwidget.AppWidgetHostView
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewOutlineProvider
+
+/** AppWidgetHostView that displays in communal hub with support for rounded corners. */
+class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context) {
+    // Mutable corner radius.
+    var enforcedCornerRadius: Float
+
+    // Mutable `Rect`. The size will be mutated when the widget is reapplied.
+    var enforcedRectangle: Rect
+
+    init {
+        enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context)
+        enforcedRectangle = Rect()
+    }
+
+    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+        super.onLayout(changed, l, t, r, b)
+
+        enforceRoundedCorners()
+    }
+
+    private val cornerRadiusEnforcementOutline: ViewOutlineProvider =
+        object : ViewOutlineProvider() {
+            override fun getOutline(view: View?, outline: Outline) {
+                if (enforcedRectangle.isEmpty || enforcedCornerRadius <= 0) {
+                    outline.setEmpty()
+                } else {
+                    outline.setRoundRect(enforcedRectangle, enforcedCornerRadius)
+                }
+            }
+        }
+
+    private fun enforceRoundedCorners() {
+        if (enforcedCornerRadius <= 0) {
+            resetRoundedCorners()
+            return
+        }
+        val background: View? = RoundedCornerEnforcement.findBackground(this)
+        if (background == null || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+            resetRoundedCorners()
+            return
+        }
+        RoundedCornerEnforcement.computeRoundedRectangle(this, background, enforcedRectangle)
+        outlineProvider = cornerRadiusEnforcementOutline
+        clipToOutline = true
+        invalidateOutline()
+    }
+
+    private fun resetRoundedCorners() {
+        outlineProvider = ViewOutlineProvider.BACKGROUND
+        clipToOutline = false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
new file mode 100644
index 0000000..abda44b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.annotation.IdRes
+import android.annotation.Nullable
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.BuildCompat.isAtLeastS
+import com.android.systemui.res.R
+import kotlin.math.min
+
+/**
+ * Utilities to compute the enforced use of rounded corners on App Widgets. This is a fork of the
+ * Launcher3 source code to enforce the same visual treatment on communal hub.
+ */
+internal object RoundedCornerEnforcement {
+    /**
+     * Find the background view for a widget.
+     *
+     * @param appWidget the view containing the App Widget (typically the instance of
+     *   [CommunalAppWidgetHostView]).
+     */
+    fun findBackground(appWidget: View): View? {
+        val backgrounds = findViewsWithId(appWidget, R.id.background)
+        if (backgrounds.size == 1) {
+            return backgrounds[0]
+        }
+        // Really, the argument should contain the widget, so it cannot be the background.
+        if (appWidget is ViewGroup) {
+            val vg = appWidget
+            if (vg.childCount > 0) {
+                return findUndefinedBackground(vg.getChildAt(0))
+            }
+        }
+        return appWidget
+    }
+
+    /** Check whether the app widget has opted out of the enforcement. */
+    fun hasAppWidgetOptedOut(appWidget: View?, background: View): Boolean {
+        return background.id == R.id.background && background.clipToOutline
+    }
+
+    /**
+     * Computes the rounded rectangle needed for this app widget.
+     *
+     * @param appWidget View onto which the rounded rectangle will be applied.
+     * @param background Background view. This must be either `appWidget` or a descendant of
+     *   `appWidget`.
+     * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame of
+     *   `appWidget`.
+     */
+    fun computeRoundedRectangle(appWidget: View, background: View, outRect: Rect) {
+        var background = background
+        outRect.left = 0
+        outRect.right = background.width
+        outRect.top = 0
+        outRect.bottom = background.height
+        while (background !== appWidget) {
+            outRect.offset(background.left, background.top)
+            background = background.parent as View
+        }
+    }
+
+    /** Get the radius of the rounded rectangle defined in the host's resource. */
+    private fun getOwnedEnforcedRadius(context: Context): Float {
+        val res: Resources = context.resources
+        return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius)
+    }
+
+    /**
+     * Computes the radius of the rounded rectangle that should be applied to a widget expanded in
+     * the given context.
+     */
+    fun computeEnforcedRadius(context: Context): Float {
+        if (!isAtLeastS()) {
+            return 0f
+        }
+        val res: Resources = context.resources
+        val systemRadius: Float =
+            res.getDimension(android.R.dimen.system_app_widget_background_radius)
+        val defaultRadius = getOwnedEnforcedRadius(context)
+        return min(defaultRadius, systemRadius)
+    }
+
+    private fun findViewsWithId(view: View, @IdRes viewId: Int): List<View> {
+        val output: MutableList<View> = ArrayList()
+        accumulateViewsWithId(view, viewId, output)
+        return output
+    }
+
+    // Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
+    private fun accumulateViewsWithId(view: View, @IdRes viewId: Int, output: MutableList<View>) {
+        if (view.id == viewId) {
+            output.add(view)
+            return
+        }
+        if (view is ViewGroup) {
+            val vg = view
+            for (i in 0 until vg.childCount) {
+                accumulateViewsWithId(vg.getChildAt(i), viewId, output)
+            }
+        }
+    }
+
+    private fun isViewVisible(view: View): Boolean {
+        return if (view.visibility != View.VISIBLE) {
+            false
+        } else !view.willNotDraw() || view.foreground != null || view.background != null
+    }
+
+    @Nullable
+    private fun findUndefinedBackground(current: View): View? {
+        if (current.visibility != View.VISIBLE) {
+            return null
+        }
+        if (isViewVisible(current)) {
+            return current
+        }
+        var lastVisibleView: View? = null
+        // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
+        // something, or a ViewGroup that contains more than one view.
+        if (current is ViewGroup) {
+            val vg = current
+            for (i in 0 until vg.childCount) {
+                val visibleView = findUndefinedBackground(vg.getChildAt(i))
+                if (visibleView != null) {
+                    if (lastVisibleView != null) {
+                        return current // At least two visible children
+                    }
+                    lastVisibleView = visibleView
+                }
+            }
+        }
+        return lastVisibleView
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ea8ba10..846736c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -102,6 +102,12 @@
             default = true
         )
 
+    /** Only notify group expansion listeners when a change happens. */
+    // TODO(b/292213543): Tracking Bug
+    @JvmField
+    val NOTIFICATION_GROUP_EXPANSION_CHANGE =
+            releasedFlag("notification_group_expansion_change")
+
     // TODO(b/301955929)
     @JvmField
     val NOTIF_LS_BACKGROUND_THREAD =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index b373f85..fede479 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -53,14 +53,14 @@
         @SysUISingleton
         @FaceAuthTableLog
         fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer {
-            return factory.create("FaceAuthTableLog", 100)
+            return factory.create("FaceAuthTableLog", 400)
         }
 
         @Provides
         @SysUISingleton
         @FaceDetectTableLog
         fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer {
-            return factory.create("FaceDetectTableLog", 100)
+            return factory.create("FaceDetectTableLog", 400)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 704ebdd..d012d24 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -40,11 +40,13 @@
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -54,7 +56,10 @@
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 
 /** Defines interface for classes that encapsulate application state for the keyguard. */
@@ -208,6 +213,12 @@
     val clockShouldBeCentered: Flow<Boolean>
 
     /**
+     * Whether the primary authentication is required for the given user due to lockdown or
+     * encryption after reboot.
+     */
+    val isEncryptedOrLockdown: Flow<Boolean>
+
+    /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
      * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
@@ -279,6 +290,7 @@
     @Application private val scope: CoroutineScope,
     private val systemClock: SystemClock,
     facePropertyRepository: FacePropertyRepository,
+    private val userTracker: UserTracker,
 ) : KeyguardRepository {
     private val _dismissAction: MutableStateFlow<DismissAction> =
         MutableStateFlow(DismissAction.None)
@@ -544,6 +556,24 @@
         awaitClose { dozeTransitionListener.removeCallback(callback) }
     }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
+    override val isEncryptedOrLockdown: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardUpdateMonitorCallback() {
+                        override fun onStrongAuthStateChanged(userId: Int) {
+                            trySend(userId)
+                        }
+                    }
+
+                keyguardUpdateMonitor.registerCallback(callback)
+
+                awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+            }
+            .filter { userId -> userId == userTracker.userId }
+            .onStart { emit(userTracker.userId) }
+            .mapLatest { userId -> keyguardUpdateMonitor.isEncryptedOrLockdown(userId) }
+
     override fun isKeyguardShowing(): Boolean {
         return keyguardStateController.isShowing
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6eb3b64..6170356 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -264,6 +264,12 @@
         }
     }
 
+    /**
+     * Whether the primary authentication is required for the given user due to lockdown or
+     * encryption after reboot.
+     */
+    val isEncryptedOrLockdown: Flow<Boolean> = repository.isEncryptedOrLockdown
+
     fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
         return dozeTransitionModel.filter { states.contains(it.to) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 2a68f26..0c0eb8a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -24,6 +24,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.LEFT
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.RIGHT
+import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -96,6 +97,11 @@
             constrainHeight(R.id.end_button, height)
             connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin)
             connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
+
+            // The constraint set visibility for start and end button are default visible, set to
+            // ignore so the view's own initial visibility (invisible) is used
+            setVisibilityMode(R.id.start_button, VISIBILITY_MODE_IGNORE)
+            setVisibilityMode(R.id.end_button, VISIBILITY_MODE_IGNORE)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 0d641ac..58e0428 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -624,7 +624,7 @@
             }
 
             if (!mIsEnabled) {
-                mGestureNavigationSettingsObserver.unregister();
+                mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::unregister);
                 if (DEBUG_MISSING_GESTURE) {
                     Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");
                 }
@@ -642,7 +642,7 @@
                 }
 
             } else {
-                mGestureNavigationSettingsObserver.register();
+                mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::register);
                 updateDisplaySize();
                 if (DEBUG_MISSING_GESTURE) {
                     Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 1c9f5fd..6fb5174 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -5,6 +5,7 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Animatable2;
@@ -41,9 +42,9 @@
 
     private final ArrayList<Integer> mQueuedPositions = new ArrayList<>();
 
-    private final int mPageIndicatorWidth;
-    private final int mPageIndicatorHeight;
-    private final int mPageDotWidth;
+    private int mPageIndicatorWidth;
+    private int mPageIndicatorHeight;
+    private int mPageDotWidth;
     private @NonNull ColorStateList mTint;
 
     private int mPosition = -1;
@@ -93,6 +94,35 @@
         });
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateResources();
+    }
+
+    private void updateResources() {
+        Resources res = getResources();
+        boolean changed = false;
+        int pageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width);
+        if (pageIndicatorWidth != mPageIndicatorWidth) {
+            mPageIndicatorWidth = pageIndicatorWidth;
+            changed = true;
+        }
+        int pageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height);
+        if (pageIndicatorHeight != mPageIndicatorHeight) {
+            mPageIndicatorHeight = pageIndicatorHeight;
+            changed = true;
+        }
+        int pageIndicatorDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width);
+        if (pageIndicatorDotWidth != mPageDotWidth) {
+            mPageDotWidth = pageIndicatorDotWidth;
+            changed = true;
+        }
+        if (changed) {
+            invalidate();
+        }
+    }
+
     public void setNumPages(int numPages) {
         setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
         if (numPages == getChildCount()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index c908e6e..5a872d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -35,6 +35,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settingslib.development.DevelopmentSettingsEnabler;
+import com.android.systemui.FontSizeUtils;
 import com.android.systemui.res.R;
 
 /**
@@ -109,11 +110,31 @@
 
     private void updateResources() {
         updateFooterAnimator();
+        updateEditButtonResources();
+        updateBuildTextResources();
         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+        lp.height = getResources().getDimensionPixelSize(R.dimen.qs_footer_height);
+        int sideMargin = getResources().getDimensionPixelSize(R.dimen.qs_footer_margin);
+        lp.leftMargin = sideMargin;
+        lp.rightMargin = sideMargin;
         lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
         setLayoutParams(lp);
     }
 
+    private void updateEditButtonResources() {
+        int size = getResources().getDimensionPixelSize(R.dimen.qs_footer_action_button_size);
+        int padding = getResources().getDimensionPixelSize(R.dimen.qs_footer_icon_padding);
+        MarginLayoutParams lp = (MarginLayoutParams) mEditButton.getLayoutParams();
+        lp.height = size;
+        lp.width = size;
+        mEditButton.setLayoutParams(lp);
+        mEditButton.setPadding(padding, padding, padding, padding);
+    }
+
+    private void updateBuildTextResources() {
+        FontSizeUtils.updateFontSizeFromStyle(mBuildText, R.style.TextAppearance_QS_Status_Build);
+    }
+
     private void updateFooterAnimator() {
         mFooterAnimator = createFooterAnimator();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index aff4a67..2077d73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -88,9 +88,9 @@
     /** Called when the expansion of the Quick Settings changed. */
     fun onQuickSettingsExpansionChanged(expansion: Float, isInSplitShade: Boolean) {
         if (isInSplitShade) {
-            // In split shade, we want to fade in the background only at the very end (see
-            // b/240563302).
-            val delay = 0.99f
+            // In split shade, we want to fade in the background when the QS background starts to
+            // show.
+            val delay = 0.15f
             _alpha.value = expansion
             _backgroundAlpha.value = max(0f, expansion - delay) / (1f - delay)
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
index 1805eb1..1a06c38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
@@ -22,12 +22,14 @@
 import android.view.View
 import android.view.View.AccessibilityDelegate
 import android.view.View.GONE
+import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.accessibility.AccessibilityNodeInfo
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
 import android.widget.ImageView
+import android.widget.ProgressBar
 import android.widget.Switch
 import android.widget.TextView
 import androidx.recyclerview.widget.AsyncListDiffer
@@ -92,6 +94,8 @@
     private lateinit var pairNewDeviceButton: View
     private lateinit var deviceListView: RecyclerView
     private lateinit var scrollViewContent: View
+    private lateinit var progressBarAnimation: ProgressBar
+    private lateinit var progressBarBackground: View
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -122,6 +126,8 @@
             scrollViewContent = this
             layoutParams.height = cachedContentHeight
         }
+        progressBarAnimation = requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
+        progressBarBackground = requireViewById(R.id.bluetooth_tile_dialog_progress_background)
     }
 
     override fun start() {
@@ -135,6 +141,17 @@
         super.dismiss()
     }
 
+    internal suspend fun animateProgressBar(animate: Boolean) {
+        withContext(mainDispatcher) {
+            if (animate) {
+                showProgressBar()
+            } else {
+                delay(PROGRESS_BAR_ANIMATION_DURATION_MS)
+                hideProgressBar()
+            }
+        }
+    }
+
     internal suspend fun onDeviceItemUpdated(
         deviceItem: List<DeviceItem>,
         showSeeAll: Boolean,
@@ -190,6 +207,28 @@
         }
     }
 
+    private fun showProgressBar() {
+        if (
+            ::progressBarAnimation.isInitialized &&
+                ::progressBarBackground.isInitialized &&
+                progressBarAnimation.visibility != VISIBLE
+        ) {
+            progressBarAnimation.visibility = VISIBLE
+            progressBarBackground.visibility = INVISIBLE
+        }
+    }
+
+    private fun hideProgressBar() {
+        if (
+            ::progressBarAnimation.isInitialized &&
+                ::progressBarBackground.isInitialized &&
+                progressBarAnimation.visibility != INVISIBLE
+        ) {
+            progressBarAnimation.visibility = INVISIBLE
+            progressBarBackground.visibility = VISIBLE
+        }
+    }
+
     internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) :
         RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
 
@@ -300,6 +339,7 @@
         const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
         const val DISABLED_ALPHA = 0.3f
         const val ENABLED_ALPHA = 1f
+        const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
 
         private fun Boolean.toInt(): Int {
             return if (this) 1 else 0
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index 6d08f59..194e7bc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
@@ -105,6 +105,41 @@
                     deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
                 }
 
+                // deviceItemUpdate is emitted when device item list is done fetching, update UI and
+                // stop the progress bar.
+                deviceItemInteractor.deviceItemUpdate
+                    .onEach {
+                        updateDialogUiJob?.cancel()
+                        updateDialogUiJob = launch {
+                            dialog.apply {
+                                onDeviceItemUpdated(
+                                    it.take(MAX_DEVICE_ITEM_ENTRY),
+                                    showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
+                                    showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+                                )
+                                animateProgressBar(false)
+                            }
+                        }
+                    }
+                    .launchIn(this)
+
+                // deviceItemUpdateRequest is emitted when a bluetooth callback is called, re-fetch
+                // the device item list and animiate the progress bar.
+                deviceItemInteractor.deviceItemUpdateRequest
+                    .onEach {
+                        dialog.animateProgressBar(true)
+                        updateDeviceItemJob?.cancel()
+                        updateDeviceItemJob = launch {
+                            deviceItemInteractor.updateDeviceItems(
+                                context,
+                                DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED
+                            )
+                        }
+                    }
+                    .launchIn(this)
+
+                // bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
+                // the device item list.
                 bluetoothStateInteractor.bluetoothStateUpdate
                     .filterNotNull()
                     .onEach {
@@ -119,39 +154,21 @@
                     }
                     .launchIn(this)
 
-                deviceItemInteractor.deviceItemUpdateRequest
-                    .onEach {
-                        updateDeviceItemJob?.cancel()
-                        updateDeviceItemJob = launch {
-                            deviceItemInteractor.updateDeviceItems(
-                                context,
-                                DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED
-                            )
-                        }
-                    }
-                    .launchIn(this)
-
-                deviceItemInteractor.deviceItemUpdate
-                    .onEach {
-                        updateDialogUiJob?.cancel()
-                        updateDialogUiJob = launch {
-                            dialog.onDeviceItemUpdated(
-                                it.take(MAX_DEVICE_ITEM_ENTRY),
-                                showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
-                                showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
-                            )
-                        }
-                    }
-                    .launchIn(this)
-
+                // bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
+                // send the new value to the bluetoothStateInteractor and animate the progress bar.
                 dialog.bluetoothStateToggle
-                    .onEach { bluetoothStateInteractor.isBluetoothEnabled = it }
+                    .onEach {
+                        dialog.animateProgressBar(true)
+                        bluetoothStateInteractor.isBluetoothEnabled = it
+                    }
                     .launchIn(this)
 
+                // deviceItemClick is emitted when user clicked on a device item.
                 dialog.deviceItemClick
                     .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) }
                     .launchIn(this)
 
+                // contentHeight is emitted when the dialog is dismissed.
                 dialog.contentHeight
                     .onEach {
                         withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index d0da945..9af2d58 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -20,15 +20,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
-import com.android.systemui.media.dagger.MediaModule
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
-import javax.inject.Named
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -46,7 +41,6 @@
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val notifications: NotificationsPlaceholderViewModel,
     val mediaDataManager: MediaDataManager,
-    @Named(MediaModule.QUICK_QS_PANEL) private val mediaHost: MediaHost,
 ) {
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
@@ -83,12 +77,6 @@
         }
     }
 
-    init {
-        mediaHost.expansion = MediaHostState.EXPANDED
-        mediaHost.showsOnlyActiveMedia = true
-        mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
-    }
-
     fun isMediaVisible(): Boolean {
         // TODO(b/296122467): handle updates to carousel visibility while scene is still visible
         return mediaDataManager.hasActiveMediaOrRecommendation()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
deleted file mode 100644
index cfbd015..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.SystemClock;
-
-import java.util.stream.Stream;
-
-/**
- * A manager which contains notification alerting functionality, providing methods to add and
- * remove notifications that appear on screen for a period of time and dismiss themselves at the
- * appropriate time.  These include heads up notifications and ambient pulses.
- */
-public abstract class AlertingNotificationManager {
-    private static final String TAG = "AlertNotifManager";
-    protected final SystemClock mSystemClock;
-    protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>();
-    protected final HeadsUpManagerLogger mLogger;
-
-    protected int mMinimumDisplayTime;
-    protected int mStickyForSomeTimeAutoDismissTime;
-    protected int mAutoDismissTime;
-    private DelayableExecutor mExecutor;
-
-    public AlertingNotificationManager(HeadsUpManagerLogger logger,
-            SystemClock systemClock, @Main DelayableExecutor executor) {
-        mLogger = logger;
-        mExecutor = executor;
-        mSystemClock = systemClock;
-    }
-
-    /**
-     * Called when posting a new notification that should alert the user and appear on screen.
-     * Adds the notification to be managed.
-     * @param entry entry to show
-     */
-    public void showNotification(@NonNull NotificationEntry entry) {
-        mLogger.logShowNotification(entry);
-        addAlertEntry(entry);
-        updateNotification(entry.getKey(), true /* alert */);
-        entry.setInterruption();
-    }
-
-    /**
-     * Try to remove the notification.  May not succeed if the notification has not been shown long
-     * enough and needs to be kept around.
-     * @param key the key of the notification to remove
-     * @param releaseImmediately force a remove regardless of earliest removal time
-     * @return true if notification is removed, false otherwise
-     */
-    public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
-        mLogger.logRemoveNotification(key, releaseImmediately);
-        AlertEntry alertEntry = mAlertEntries.get(key);
-        if (alertEntry == null) {
-            return true;
-        }
-        if (releaseImmediately || canRemoveImmediately(key)) {
-            removeAlertEntry(key);
-        } else {
-            alertEntry.removeAsSoonAsPossible();
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Called when the notification state has been updated.
-     * @param key the key of the entry that was updated
-     * @param alert whether the notification should alert again and force reevaluation of
-     *              removal time
-     */
-    public void updateNotification(@NonNull String key, boolean alert) {
-        AlertEntry alertEntry = mAlertEntries.get(key);
-        mLogger.logUpdateNotification(key, alert, alertEntry != null);
-        if (alertEntry == null) {
-            // the entry was released before this update (i.e by a listener) This can happen
-            // with the groupmanager
-            return;
-        }
-
-        alertEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        if (alert) {
-            alertEntry.updateEntry(true /* updatePostTime */, "updateNotification");
-        }
-    }
-
-    /**
-     * Clears all managed notifications.
-     */
-    public void releaseAllImmediately() {
-        mLogger.logReleaseAllImmediately();
-        // A copy is necessary here as we are changing the underlying map.  This would cause
-        // undefined behavior if we iterated over the key set directly.
-        ArraySet<String> keysToRemove = new ArraySet<>(mAlertEntries.keySet());
-        for (String key : keysToRemove) {
-            removeAlertEntry(key);
-        }
-    }
-
-    /**
-     * Returns the entry if it is managed by this manager.
-     * @param key key of notification
-     * @return the entry
-     */
-    @Nullable
-    public NotificationEntry getEntry(@NonNull String key) {
-        AlertEntry entry = mAlertEntries.get(key);
-        return entry != null ? entry.mEntry : null;
-    }
-
-    /**
-     * Returns the stream of all current notifications managed by this manager.
-     * @return all entries
-     */
-    @NonNull
-    public Stream<NotificationEntry> getAllEntries() {
-        return mAlertEntries.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
-    }
-
-    /**
-     * Whether or not there are any active alerting notifications.
-     * @return true if there is an alert, false otherwise
-     */
-    public boolean hasNotifications() {
-        return !mAlertEntries.isEmpty();
-    }
-
-    /**
-     * Whether or not the given notification is alerting and managed by this manager.
-     * @return true if the notification is alerting
-     */
-    public boolean isAlerting(@NonNull String key) {
-        return mAlertEntries.containsKey(key);
-    }
-
-    /**
-     * Gets the flag corresponding to the notification content view this alert manager will show.
-     *
-     * @return flag corresponding to the content view
-     */
-    public abstract @InflationFlag int getContentFlag();
-
-    /**
-     * Add a new entry and begin managing it.
-     * @param entry the entry to add
-     */
-    protected final void addAlertEntry(@NonNull NotificationEntry entry) {
-        AlertEntry alertEntry = createAlertEntry();
-        alertEntry.setEntry(entry);
-        mAlertEntries.put(entry.getKey(), alertEntry);
-        onAlertEntryAdded(alertEntry);
-        entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        entry.setIsAlerting(true);
-    }
-
-    /**
-     * Manager-specific logic that should occur when an entry is added.
-     * @param alertEntry alert entry added
-     */
-    protected abstract void onAlertEntryAdded(@NonNull AlertEntry alertEntry);
-
-    /**
-     * Remove a notification and reset the alert entry.
-     * @param key key of notification to remove
-     */
-    protected final void removeAlertEntry(@NonNull String key) {
-        AlertEntry alertEntry = mAlertEntries.get(key);
-        if (alertEntry == null) {
-            return;
-        }
-        NotificationEntry entry = alertEntry.mEntry;
-
-        // If the notification is animating, we will remove it at the end of the animation.
-        if (entry != null && entry.isExpandAnimationRunning()) {
-            return;
-        }
-        entry.demoteStickyHun();
-        mAlertEntries.remove(key);
-        onAlertEntryRemoved(alertEntry);
-        entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        alertEntry.reset();
-    }
-
-    /**
-     * Manager-specific logic that should occur when an alert entry is removed.
-     * @param alertEntry alert entry removed
-     */
-    protected abstract void onAlertEntryRemoved(@NonNull AlertEntry alertEntry);
-
-    /**
-     * Returns a new alert entry instance.
-     * @return a new AlertEntry
-     */
-    protected AlertEntry createAlertEntry() {
-        return new AlertEntry();
-    }
-
-    /**
-     * Whether or not the alert can be removed currently.  If it hasn't been on screen long enough
-     * it should not be removed unless forced
-     * @param key the key to check if removable
-     * @return true if the alert entry can be removed
-     */
-    public boolean canRemoveImmediately(String key) {
-        AlertEntry alertEntry = mAlertEntries.get(key);
-        return alertEntry == null || alertEntry.wasShownLongEnough()
-                || alertEntry.mEntry.isRowDismissed();
-    }
-
-    /**
-     * @param key
-     * @return true if the entry is (pinned and expanded) or (has an active remote input)
-     */
-    public boolean isSticky(String key) {
-        AlertEntry alerting = mAlertEntries.get(key);
-        if (alerting != null) {
-            return alerting.isSticky();
-        }
-        return false;
-    }
-
-    /**
-     * @param key
-     * @return When a HUN entry should be removed in milliseconds from now
-     */
-    public long getEarliestRemovalTime(String key) {
-        AlertEntry alerting = mAlertEntries.get(key);
-        if (alerting != null) {
-            return Math.max(0, alerting.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
-        }
-        return 0;
-    }
-
-    protected class AlertEntry implements Comparable<AlertEntry> {
-        @Nullable public NotificationEntry mEntry;
-        public long mPostTime;
-        public long mEarliestRemovalTime;
-
-        @Nullable protected Runnable mRemoveAlertRunnable;
-        @Nullable private Runnable mCancelRemoveAlertRunnable;
-
-        public void setEntry(@NonNull final NotificationEntry entry) {
-            setEntry(entry, () -> removeAlertEntry(entry.getKey()));
-        }
-
-        public void setEntry(@NonNull final NotificationEntry entry,
-                @Nullable Runnable removeAlertRunnable) {
-            mEntry = entry;
-            mRemoveAlertRunnable = removeAlertRunnable;
-
-            mPostTime = calculatePostTime();
-            updateEntry(true /* updatePostTime */, "setEntry");
-        }
-
-        /**
-         * Updates an entry's removal time.
-         * @param updatePostTime whether or not to refresh the post time
-         */
-        public void updateEntry(boolean updatePostTime, @Nullable String reason) {
-            mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
-
-            final long now = mSystemClock.elapsedRealtime();
-            mEarliestRemovalTime = now + mMinimumDisplayTime;
-
-            if (updatePostTime) {
-                mPostTime = Math.max(mPostTime, now);
-            }
-
-            if (isSticky()) {
-                removeAutoRemovalCallbacks("updateEntry (sticky)");
-                return;
-            }
-
-            final long finishTime = calculateFinishTime();
-            final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
-            scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
-        }
-
-        /**
-         * Whether or not the notification is "sticky" i.e. should stay on screen regardless
-         * of the timer (forever) and should be removed externally.
-         * @return true if the notification is sticky
-         */
-        public boolean isSticky() {
-            // This implementation is overridden by HeadsUpManager HeadsUpEntry #isSticky
-            return false;
-        }
-
-        public boolean isStickyForSomeTime() {
-            // This implementation is overridden by HeadsUpManager HeadsUpEntry #isStickyForSomeTime
-            return false;
-        }
-
-        /**
-         * Whether the notification has befen on screen long enough and can be removed.
-         * @return true if the notification has been on screen long enough
-         */
-        public boolean wasShownLongEnough() {
-            return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
-        }
-
-        @Override
-        public int compareTo(@NonNull AlertEntry alertEntry) {
-            return (mPostTime < alertEntry.mPostTime)
-                    ? 1 : ((mPostTime == alertEntry.mPostTime)
-                            ? mEntry.getKey().compareTo(alertEntry.mEntry.getKey()) : -1);
-        }
-
-        public void reset() {
-            removeAutoRemovalCallbacks("reset()");
-            mEntry = null;
-            mRemoveAlertRunnable = null;
-        }
-
-        /**
-         * Clear any pending removal runnables.
-         */
-        public void removeAutoRemovalCallbacks(@Nullable String reason) {
-            final boolean removed = removeAutoRemovalCallbackInternal();
-
-            if (removed) {
-                mLogger.logAutoRemoveCanceled(mEntry, reason);
-            }
-        }
-
-        private void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
-            if (mRemoveAlertRunnable == null) {
-                Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
-                return;
-            }
-
-            final boolean removed = removeAutoRemovalCallbackInternal();
-
-            if (removed) {
-                mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
-            } else {
-                mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
-            }
-
-
-            mCancelRemoveAlertRunnable = mExecutor.executeDelayed(mRemoveAlertRunnable,
-                    delayMillis);
-        }
-
-        private boolean removeAutoRemovalCallbackInternal() {
-            final boolean scheduled = (mCancelRemoveAlertRunnable != null);
-
-            if (scheduled) {
-                mCancelRemoveAlertRunnable.run();
-                mCancelRemoveAlertRunnable = null;
-            }
-
-            return scheduled;
-        }
-
-        /**
-         * Remove the alert at the earliest allowed removal time.
-         */
-        public void removeAsSoonAsPossible() {
-            if (mRemoveAlertRunnable != null) {
-                final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
-                scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
-            }
-        }
-
-        /**
-         * Calculate what the post time of a notification is at some current time.
-         * @return the post time
-         */
-        protected long calculatePostTime() {
-            return mSystemClock.elapsedRealtime();
-        }
-
-        /**
-         * @return When the notification should auto-dismiss itself, based on
-         * {@link SystemClock#elapsedRealtime()}
-         */
-        protected long calculateFinishTime() {
-            // Overridden by HeadsUpManager HeadsUpEntry #calculateFinishTime
-            return 0;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 7a2e82f..bb6ee24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -645,6 +645,10 @@
                 + "slot='" + mSlot + "' alpha=" + getAlpha() + " icon=" + mIcon
                 + " visibleState=" + getVisibleStateString(getVisibleState())
                 + " iconColor=#" + Integer.toHexString(mIconColor)
+                + " staticDrawableColor=#" + Integer.toHexString(mDrawableColor)
+                + " decorColor=#" + Integer.toHexString(mDecorColor)
+                + " animationStartColor=#" + Integer.toHexString(mAnimationStartColor)
+                + " currentSetColor=#" + Integer.toHexString(mCurrentSetColor)
                 + " notification=" + mNotification + ')';
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 96279e2..5983fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -157,9 +157,9 @@
             val summaryEntry = notificationEntry.parent?.summary
 
             return when {
-                headsUpManager.isAlerting(notificationKey) -> notification
+                headsUpManager.isHeadsUpEntry(notificationKey) -> notification
                 summaryEntry == null -> null
-                headsUpManager.isAlerting(summaryEntry.key) -> summaryEntry.row
+                headsUpManager.isHeadsUpEntry(summaryEntry.key) -> summaryEntry.row
                 else -> null
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cfe9fbe..cdacb10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -171,7 +171,7 @@
     private int mBucket = BUCKET_ALERTING;
     @Nullable private Long mPendingAnimationDuration;
     private boolean mIsMarkedForUserTriggeredMovement;
-    private boolean mIsAlerting;
+    private boolean mIsHeadsUpEntry;
 
     public boolean mRemoteEditImeAnimatingAway;
     public boolean mRemoteEditImeVisible;
@@ -965,12 +965,12 @@
         mIsMarkedForUserTriggeredMovement = marked;
     }
 
-    public void setIsAlerting(boolean isAlerting) {
-        mIsAlerting = isAlerting;
+    public void setIsHeadsUpEntry(boolean isHeadsUpEntry) {
+        mIsHeadsUpEntry = isHeadsUpEntry;
     }
 
-    public boolean isAlerting() {
-        return mIsAlerting;
+    public boolean isHeadsUpEntry() {
+        return mIsHeadsUpEntry;
     }
 
     /** Set whether this notification is currently used to animate a launch. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 314566e..46806e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -221,7 +221,7 @@
                         wasUpdated = false,
                         shouldHeadsUpEver = false,
                         shouldHeadsUpAgain = false,
-                        isAlerting = mHeadsUpManager.isAlerting(logicalSummary.key),
+                        isAlerting = mHeadsUpManager.isHeadsUpEntry(logicalSummary.key),
                         isBinding = isEntryBinding(logicalSummary),
                 )
                 // If we transfer the alert and the summary isn't even attached, that means we
@@ -268,7 +268,7 @@
                         wasUpdated = false,
                         shouldHeadsUpEver = true,
                         shouldHeadsUpAgain = true,
-                        isAlerting = mHeadsUpManager.isAlerting(childToReceiveParentAlert.key),
+                        isAlerting = mHeadsUpManager.isHeadsUpEntry(childToReceiveParentAlert.key),
                         isBinding = isEntryBinding(childToReceiveParentAlert),
                 )
                 handlePostedEntry(
@@ -425,7 +425,7 @@
             val shouldHeadsUpEver =
                 mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt
             val shouldHeadsUpAgain = shouldHunAgain(entry)
-            val isAlerting = mHeadsUpManager.isAlerting(entry.key)
+            val isAlerting = mHeadsUpManager.isHeadsUpEntry(entry.key)
             val isBinding = isEntryBinding(entry)
             val posted = mPostedEntries.compute(entry.key) { _, value ->
                 value?.also { update ->
@@ -469,7 +469,7 @@
             mEntriesUpdateTimes.remove(entry.key)
             cancelHeadsUpBind(entry)
             val entryKey = entry.key
-            if (mHeadsUpManager.isAlerting(entryKey)) {
+            if (mHeadsUpManager.isHeadsUpEntry(entryKey)) {
                 // TODO: This should probably know the RemoteInputCoordinator's conditions,
                 //  or otherwise reference that coordinator's state, rather than replicate its logic
                 val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) &&
@@ -719,7 +719,7 @@
      * Whether the notification is already alerting or binding so that it can imminently alert
      */
     private fun isAttemptingToShowHun(entry: ListEntry) =
-        mHeadsUpManager.isAlerting(entry.key) || isEntryBinding(entry)
+        mHeadsUpManager.isHeadsUpEntry(entry.key) || isEntryBinding(entry)
 
     /**
      * Whether the notification is already alerting/binding per [isAttemptingToShowHun] OR if it
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index a0129ff..8189fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -134,7 +134,7 @@
     private final NotifStabilityManager mNotifStabilityManager =
             new NotifStabilityManager("VisualStabilityCoordinator") {
                 private boolean canMoveForHeadsUp(NotificationEntry entry) {
-                    return entry != null && mHeadsUpManager.isAlerting(entry.getKey())
+                    return entry != null && mHeadsUpManager.isHeadsUpEntry(entry.getKey())
                             && !mVisibilityLocationProvider.isInVisibleLocation(entry);
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index e71d80c..5743ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -68,7 +68,7 @@
     @NonNull
     private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
         int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
-        if (mHeadsUpManager.isAlerting(entry.getKey())) {
+        if (mHeadsUpManager.isHeadsUpEntry(entry.getKey())) {
             dismissalSurface = NotificationStats.DISMISSAL_PEEK;
         } else if (mStatusBarStateController.isDozing()) {
             dismissalSurface = NotificationStats.DISMISSAL_AOD;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 3cdb2cd..2d5afd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -21,6 +21,8 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -51,11 +53,14 @@
      */
     private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
 
+    private final FeatureFlags mFeatureFlags;
+
     @Inject
     public GroupExpansionManagerImpl(DumpManager dumpManager,
-            GroupMembershipManager groupMembershipManager) {
+            GroupMembershipManager groupMembershipManager, FeatureFlags featureFlags) {
         mDumpManager = dumpManager;
         mGroupMembershipManager = groupMembershipManager;
+        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -81,8 +86,10 @@
     };
 
     public void attach(NotifPipeline pipeline) {
-        mDumpManager.registerDumpable(this);
-        pipeline.addOnBeforeRenderListListener(mNotifTracker);
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
+            mDumpManager.registerDumpable(this);
+            pipeline.addOnBeforeRenderListListener(mNotifTracker);
+        }
     }
 
     @Override
@@ -98,7 +105,8 @@
     @Override
     public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
         NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
-        if (entry.getParent() == null) {
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)
+                && entry.getParent() == null) {
             if (expanded) {
                 throw new IllegalArgumentException("Cannot expand group that is not attached");
             } else {
@@ -116,7 +124,7 @@
         }
 
         // Only notify listeners if something changed.
-        if (changed) {
+        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) || changed) {
             sendOnGroupExpandedChange(entry, expanded);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index da12479..cb79353 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -22,6 +22,8 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -36,17 +38,25 @@
  */
 @SysUISingleton
 public class GroupMembershipManagerImpl implements GroupMembershipManager {
+    FeatureFlagsClassic mFeatureFlags;
+
     @Inject
-    public GroupMembershipManagerImpl() {}
+    public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) {
+        mFeatureFlags = featureFlags;
+    }
 
     @Override
     public boolean isGroupSummary(@NonNull NotificationEntry entry) {
-        if (entry.getParent() == null) {
-            // The entry is not attached, so it doesn't count.
-            return false;
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
+            if (entry.getParent() == null) {
+                // The entry is not attached, so it doesn't count.
+                return false;
+            }
+            // If entry is a summary, its parent is a GroupEntry with summary = entry.
+            return entry.getParent().getSummary() == entry;
+        } else {
+            return getGroupSummary(entry) == entry;
         }
-        // If entry is a summary, its parent is a GroupEntry with summary = entry.
-        return entry.getParent().getSummary() == entry;
     }
 
     @Nullable
@@ -60,8 +70,12 @@
 
     @Override
     public boolean isChildInGroup(@NonNull NotificationEntry entry) {
-        // An entry is a child if it's not a summary or top level entry, but it is attached.
-        return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
+            // An entry is a child if it's not a summary or top level entry, but it is attached.
+            return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
+        } else {
+            return !isTopLevelEntry(entry);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt
new file mode 100644
index 0000000..44fc77f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the Async Group Header Inflation flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object AsyncGroupHeaderViewInflation {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the async inflation of group header views enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationAsyncGroupHeaderInflation()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index c527bb5..20fae88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -578,7 +578,7 @@
     }
 
     public boolean isPulsing(NotificationEntry entry) {
-        return mPulsing && entry.isAlerting();
+        return mPulsing && entry.isHeadsUpEntry();
     }
 
     public void setPulsingRow(ExpandableNotificationRow row) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index e66d9e8..b07ba3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -222,9 +222,9 @@
             mReleaseOnExpandFinish = false;
         } else {
             for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
-                if (isAlerting(entry.getKey())) {
+                if (isHeadsUpEntry(entry.getKey())) {
                     // Maybe the heads-up was removed already
-                    removeAlertEntry(entry.getKey());
+                    removeEntry(entry.getKey());
                 }
             }
         }
@@ -357,9 +357,9 @@
     private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
         mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
         for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
-            if (isAlerting(entry.getKey())) {
+            if (isHeadsUpEntry(entry.getKey())) {
                 // Maybe the heads-up was removed already
-                removeAlertEntry(entry.getKey());
+                removeEntry(entry.getKey());
             }
         }
         mEntriesToRemoveWhenReorderingAllowed.clear();
@@ -370,14 +370,14 @@
     //  HeadsUpManager utility (protected) methods overrides:
 
     @Override
-    protected HeadsUpEntry createAlertEntry() {
+    protected HeadsUpEntry createHeadsUpEntry() {
         return mEntryPool.acquire();
     }
 
     @Override
-    protected void onAlertEntryRemoved(AlertEntry alertEntry) {
-        super.onAlertEntryRemoved(alertEntry);
-        mEntryPool.release((HeadsUpEntryPhone) alertEntry);
+    protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
+        super.onEntryRemoved(headsUpEntry);
+        mEntryPool.release((HeadsUpEntryPhone) headsUpEntry);
     }
 
     @Override
@@ -403,7 +403,7 @@
 
     @Nullable
     private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
-        return (HeadsUpEntryPhone) mAlertEntries.get(key);
+        return (HeadsUpEntryPhone) mHeadsUpEntryMap.get(key);
     }
 
     @Nullable
@@ -455,7 +455,7 @@
                 } else if (mTrackingHeadsUp) {
                     mEntriesToRemoveAfterExpand.add(entry);
                 } else {
-                    removeAlertEntry(entry.getKey());
+                    removeEntry(entry.getKey());
                 }
             };
 
@@ -529,13 +529,13 @@
             mStatusBarState = newState;
             if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
                 ArrayList<String> keysToRemove = new ArrayList<>();
-                for (AlertEntry entry : mAlertEntries.values()) {
+                for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) {
                     if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
                         keysToRemove.add(entry.mEntry.getKey());
                     }
                 }
                 for (String key : keysToRemove) {
-                    removeAlertEntry(key);
+                    removeEntry(key);
                 }
             }
         }
@@ -545,7 +545,7 @@
             if (!isDozing) {
                 // Let's make sure all huns we got while dozing time out within the normal timeout
                 // duration. Otherwise they could get stuck for a very long time
-                for (AlertEntry entry : mAlertEntries.values()) {
+                for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) {
                     entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index f34a44a..be5c6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -918,8 +918,15 @@
                     }
                 }
                 icon.setVisibleState(visibleState, animationsAllowed);
-                icon.setIconColor(mOverrideIconColor ? mThemedTextColorPrimary : iconColor,
-                        needsCannedAnimation && animationsAllowed);
+                if (NotificationIconContainerRefactor.isEnabled()) {
+                    if (mOverrideIconColor) {
+                        icon.setIconColor(mThemedTextColorPrimary,
+                                /* animate= */ needsCannedAnimation && animationsAllowed);
+                    }
+                } else {
+                    icon.setIconColor(mOverrideIconColor ? mThemedTextColorPrimary : iconColor,
+                            needsCannedAnimation && animationsAllowed);
+                }
                 if (animate) {
                     animateTo(icon, animationProperties);
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 9da6111..4ee061d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -562,7 +562,7 @@
 
     private void removeHunAfterClick(ExpandableNotificationRow row) {
         String key = row.getEntry().getSbn().getKey();
-        if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
+        if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUpEntry(key)) {
             // Release the HUN notification to the shade.
             if (mPresenter.isPresenterFullyCollapsed()) {
                 HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(row, true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index e971128..1528c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -26,6 +26,9 @@
 import android.database.ContentObserver;
 import android.os.Handler;
 import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.logging.MetricsLogger;
@@ -34,7 +37,6 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.res.R;
-import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.util.ListenerSet;
@@ -43,14 +45,14 @@
 import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
+import java.util.stream.Stream;
 
 /**
  * A manager which handles heads up notifications which is a special mode where
  * they simply peek from the top of the screen.
  */
-public abstract class BaseHeadsUpManager extends AlertingNotificationManager implements
-        HeadsUpManager {
-    private static final String TAG = "HeadsUpManager";
+public abstract class BaseHeadsUpManager implements HeadsUpManager {
+    private static final String TAG = "BaseHeadsUpManager";
     private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
 
     protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
@@ -67,6 +69,14 @@
 
     private final UiEventLogger mUiEventLogger;
 
+    protected final SystemClock mSystemClock;
+    protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
+    protected final HeadsUpManagerLogger mLogger;
+    protected int mMinimumDisplayTime;
+    protected int mStickyForSomeTimeAutoDismissTime;
+    protected int mAutoDismissTime;
+    protected DelayableExecutor mExecutor;
+
     /**
      * Enum entry for notification peek logged from this class.
      */
@@ -91,7 +101,9 @@
             @Main DelayableExecutor executor,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger) {
-        super(logger, systemClock, executor);
+        mLogger = logger;
+        mExecutor = executor;
+        mSystemClock = systemClock;
         mContext = context;
         mAccessibilityMgr = accessibilityManagerWrapper;
         mUiEventLogger = uiEventLogger;
@@ -138,20 +150,136 @@
         mListeners.remove(listener);
     }
 
-    /** Updates the notification with the given key. */
-    public void updateNotification(@NonNull String key, boolean alert) {
-        super.updateNotification(key, alert);
-        HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
-        if (alert && headsUpEntry != null) {
-            setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
+    /**
+     * Called when posting a new notification that should appear on screen.
+     * Adds the notification to be managed.
+     * @param entry entry to show
+     */
+    @Override
+    public void showNotification(@NonNull NotificationEntry entry) {
+        mLogger.logShowNotification(entry);
+        addEntry(entry);
+        updateNotification(entry.getKey(), true /* show */);
+        entry.setInterruption();
+    }
+
+    /**
+     * Try to remove the notification.  May not succeed if the notification has not been shown long
+     * enough and needs to be kept around.
+     * @param key the key of the notification to remove
+     * @param releaseImmediately force a remove regardless of earliest removal time
+     * @return true if notification is removed, false otherwise
+     */
+    @Override
+    public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
+        mLogger.logRemoveNotification(key, releaseImmediately);
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        if (headsUpEntry == null) {
+            return true;
         }
+        if (releaseImmediately || canRemoveImmediately(key)) {
+            removeEntry(key);
+        } else {
+            headsUpEntry.removeAsSoonAsPossible();
+            return false;
+        }
+        return true;
+    }
+
+
+    /**
+     * Called when the notification state has been updated.
+     * @param key the key of the entry that was updated
+     * @param show whether the notification should show again and force reevaluation of
+     *              removal time
+     */
+    public void updateNotification(@NonNull String key, boolean show) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        mLogger.logUpdateNotification(key, show, headsUpEntry != null);
+        if (headsUpEntry == null) {
+            // the entry was released before this update (i.e by a listener) This can happen
+            // with the groupmanager
+            return;
+        }
+
+        headsUpEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+
+        if (show) {
+            headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
+            if (headsUpEntry != null) {
+                setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
+            }
+        }
+    }
+
+    /**
+     * Clears all managed notifications.
+     */
+    public void releaseAllImmediately() {
+        mLogger.logReleaseAllImmediately();
+        // A copy is necessary here as we are changing the underlying map.  This would cause
+        // undefined behavior if we iterated over the key set directly.
+        ArraySet<String> keysToRemove = new ArraySet<>(mHeadsUpEntryMap.keySet());
+        for (String key : keysToRemove) {
+            removeEntry(key);
+        }
+    }
+
+    /**
+     * Returns the entry if it is managed by this manager.
+     * @param key key of notification
+     * @return the entry
+     */
+    @Nullable
+    public NotificationEntry getEntry(@NonNull String key) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        return headsUpEntry != null ? headsUpEntry.mEntry : null;
+    }
+
+    /**
+     * Returns the stream of all current notifications managed by this manager.
+     * @return all entries
+     */
+    @NonNull
+    @Override
+    public Stream<NotificationEntry> getAllEntries() {
+        return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
+    }
+
+    /**
+     * Whether or not there are any active notifications.
+     * @return true if there is an entry, false otherwise
+     */
+    @Override
+    public boolean hasNotifications() {
+        return !mHeadsUpEntryMap.isEmpty();
+    }
+
+    /**
+     * @return true if the notification is managed by this manager
+     */
+    public boolean isHeadsUpEntry(@NonNull String key) {
+        return mHeadsUpEntryMap.containsKey(key);
+    }
+
+    /**
+     * @param key
+     * @return When a HUN entry should be removed in milliseconds from now
+     */
+    @Override
+    public long getEarliestRemovalTime(String key) {
+        HeadsUpEntry entry = mHeadsUpEntryMap.get(key);
+        if (entry != null) {
+            return Math.max(0, entry.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
+        }
+        return 0;
     }
 
     protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
         final HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
         if (headsUpEntry == null) {
             // This should not happen since shouldHeadsUpBecomePinned is always called after adding
-            // the NotificationEntry into AlertingNotificationManager's mAlertEntries map.
+            // the NotificationEntry into mHeadsUpEntryMap.
             return hasFullScreenIntent(entry);
         }
         return hasFullScreenIntent(entry) && !headsUpEntry.mWasUnpinned;
@@ -190,24 +318,65 @@
         return FLAG_CONTENT_VIEW_HEADS_UP;
     }
 
-    @Override
-    protected void onAlertEntryAdded(AlertEntry alertEntry) {
-        NotificationEntry entry = alertEntry.mEntry;
+    /**
+     * Add a new entry and begin managing it.
+     * @param entry the entry to add
+     */
+    protected final void addEntry(@NonNull NotificationEntry entry) {
+        HeadsUpEntry headsUpEntry = createHeadsUpEntry();
+        headsUpEntry.setEntry(entry);
+        mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+        onEntryAdded(headsUpEntry);
+        entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        entry.setIsHeadsUpEntry(true);
+    }
+
+    /**
+     * Manager-specific logic that should occur when an entry is added.
+     * @param headsUpEntry entry added
+     */
+    protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+        NotificationEntry entry = headsUpEntry.mEntry;
         entry.setHeadsUp(true);
 
         final boolean shouldPin = shouldHeadsUpBecomePinned(entry);
-        setEntryPinned((HeadsUpEntry) alertEntry, shouldPin);
+        setEntryPinned(headsUpEntry, shouldPin);
         EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, true);
         }
     }
 
-    @Override
-    protected void onAlertEntryRemoved(AlertEntry alertEntry) {
-        NotificationEntry entry = alertEntry.mEntry;
+    /**
+     * Remove a notification and reset the entry.
+     * @param key key of notification to remove
+     */
+    protected final void removeEntry(@NonNull String key) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        if (headsUpEntry == null) {
+            return;
+        }
+        NotificationEntry entry = headsUpEntry.mEntry;
+
+        // If the notification is animating, we will remove it at the end of the animation.
+        if (entry != null && entry.isExpandAnimationRunning()) {
+            return;
+        }
+        entry.demoteStickyHun();
+        mHeadsUpEntryMap.remove(key);
+        onEntryRemoved(headsUpEntry);
+        entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        headsUpEntry.reset();
+    }
+
+    /**
+     * Manager-specific logic that should occur when an entry is removed.
+     * @param headsUpEntry entry removed
+     */
+    protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
+        NotificationEntry entry = headsUpEntry.mEntry;
         entry.setHeadsUp(false);
-        setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
+        setEntryPinned(headsUpEntry, false /* isPinned */);
         EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
         mLogger.logNotificationActuallyRemoved(entry);
         for (OnHeadsUpChangedListener listener : mListeners) {
@@ -251,8 +420,8 @@
      * Snoozes all current Heads Up Notifications.
      */
     public void snooze() {
-        for (String key : mAlertEntries.keySet()) {
-            AlertEntry entry = getHeadsUpEntry(key);
+        for (String key : mHeadsUpEntryMap.keySet()) {
+            HeadsUpEntry entry = getHeadsUpEntry(key);
             String packageName = entry.mEntry.getSbn().getPackageName();
             String snoozeKey = snoozeKey(packageName, mUser);
             mLogger.logPackageSnoozed(snoozeKey);
@@ -267,7 +436,7 @@
 
     @Nullable
     protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
-        return (HeadsUpEntry) mAlertEntries.get(key);
+        return (HeadsUpEntry) mHeadsUpEntryMap.get(key);
     }
 
     /**
@@ -281,11 +450,11 @@
 
     @Nullable
     protected HeadsUpEntry getTopHeadsUpEntry() {
-        if (mAlertEntries.isEmpty()) {
+        if (mHeadsUpEntryMap.isEmpty()) {
             return null;
         }
         HeadsUpEntry topEntry = null;
-        for (AlertEntry entry: mAlertEntries.values()) {
+        for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
             if (topEntry == null || entry.compareTo(topEntry) < 0) {
                 topEntry = (HeadsUpEntry) entry;
             }
@@ -316,7 +485,7 @@
         pw.print("  mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
         pw.print("  now="); pw.println(mSystemClock.elapsedRealtime());
         pw.print("  mUser="); pw.println(mUser);
-        for (AlertEntry entry: mAlertEntries.values()) {
+        for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
             pw.print("  HeadsUpEntry="); pw.println(entry.mEntry);
         }
         int n = mSnoozedPackages.size();
@@ -335,8 +504,8 @@
     }
 
     private boolean hasPinnedNotificationInternal() {
-        for (String key : mAlertEntries.keySet()) {
-            AlertEntry entry = getHeadsUpEntry(key);
+        for (String key : mHeadsUpEntryMap.keySet()) {
+            HeadsUpEntry entry = getHeadsUpEntry(key);
             if (entry.mEntry.isRowPinned()) {
                 return true;
             }
@@ -349,7 +518,7 @@
      * @param userUnPinned The unpinned action is trigger by user real operation.
      */
     public void unpinAll(boolean userUnPinned) {
-        for (String key : mAlertEntries.keySet()) {
+        for (String key : mHeadsUpEntryMap.keySet()) {
             HeadsUpEntry entry = getHeadsUpEntry(key);
             setEntryPinned(entry, false /* isPinned */);
             // maybe it got un sticky
@@ -384,8 +553,8 @@
         if (a == null || b == null) {
             return Boolean.compare(a == null, b == null);
         }
-        AlertEntry aEntry = getHeadsUpEntry(a.getKey());
-        AlertEntry bEntry = getHeadsUpEntry(b.getKey());
+        HeadsUpEntry aEntry = getHeadsUpEntry(a.getKey());
+        HeadsUpEntry bEntry = getHeadsUpEntry(b.getKey());
         if (aEntry == null || bEntry == null) {
             return Boolean.compare(aEntry == null, bEntry == null);
         }
@@ -419,18 +588,37 @@
         }
     }
 
+    /**
+     * Whether or not the entry can be removed currently.  If it hasn't been on screen long enough
+     * it should not be removed unless forced
+     * @param key the key to check if removable
+     * @return true if the entry can be removed
+     */
     @Override
     public boolean canRemoveImmediately(@NonNull String key) {
         HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
         if (headsUpEntry != null && headsUpEntry.mUserActionMayIndirectlyRemove) {
             return true;
         }
-        return super.canRemoveImmediately(key);
+        return headsUpEntry == null || headsUpEntry.wasShownLongEnough()
+                || headsUpEntry.mEntry.isRowDismissed();
+    }
+
+    /**
+     * @param key
+     * @return true if the entry is (pinned and expanded) or (has an active remote input)
+     */
+    @Override
+    public boolean isSticky(String key) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        if (headsUpEntry != null) {
+            return headsUpEntry.isSticky();
+        }
+        return false;
     }
 
     @NonNull
-    @Override
-    protected HeadsUpEntry createAlertEntry() {
+    protected HeadsUpEntry createHeadsUpEntry() {
         return new HeadsUpEntry();
     }
 
@@ -451,28 +639,82 @@
      * This represents a notification and how long it is in a heads up mode. It also manages its
      * lifecycle automatically when created.
      */
-    protected class HeadsUpEntry extends AlertEntry {
+    protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
         public boolean mRemoteInputActive;
         public boolean mUserActionMayIndirectlyRemove;
 
         protected boolean mExpanded;
         protected boolean mWasUnpinned;
 
-        @Override
+        @Nullable public NotificationEntry mEntry;
+        public long mPostTime;
+        public long mEarliestRemovalTime;
+
+        @Nullable protected Runnable mRemoveRunnable;
+
+        @Nullable private Runnable mCancelRemoveRunnable;
+
+        public void setEntry(@NonNull final NotificationEntry entry) {
+            setEntry(entry, () -> removeEntry(entry.getKey()));
+        }
+
+        public void setEntry(@NonNull final NotificationEntry entry,
+                @Nullable Runnable removeRunnable) {
+            mEntry = entry;
+            mRemoveRunnable = removeRunnable;
+
+            mPostTime = calculatePostTime();
+            updateEntry(true /* updatePostTime */, "setEntry");
+        }
+
+        /**
+         * Updates an entry's removal time.
+         * @param updatePostTime whether or not to refresh the post time
+         */
+        public void updateEntry(boolean updatePostTime, @Nullable String reason) {
+            mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
+
+            final long now = mSystemClock.elapsedRealtime();
+            mEarliestRemovalTime = now + mMinimumDisplayTime;
+
+            if (updatePostTime) {
+                mPostTime = Math.max(mPostTime, now);
+            }
+
+            if (isSticky()) {
+                removeAutoRemovalCallbacks("updateEntry (sticky)");
+                return;
+            }
+
+            final long finishTime = calculateFinishTime();
+            final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+            scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
+        }
+
+        /**
+         * Whether or not the notification is "sticky" i.e. should stay on screen regardless
+         * of the timer (forever) and should be removed externally.
+         * @return true if the notification is sticky
+         */
         public boolean isSticky() {
             return (mEntry.isRowPinned() && mExpanded)
                     || mRemoteInputActive
                     || hasFullScreenIntent(mEntry);
         }
 
-        @Override
         public boolean isStickyForSomeTime() {
             return mEntry.isStickyAndNotDemoted();
         }
 
-        @Override
-        public int compareTo(@NonNull AlertEntry alertEntry) {
-            HeadsUpEntry headsUpEntry = (HeadsUpEntry) alertEntry;
+        /**
+         * Whether the notification has been on screen long enough and can be removed.
+         * @return true if the notification has been on screen long enough
+         */
+        public boolean wasShownLongEnough() {
+            return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
+        }
+
+        public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
             boolean isPinned = mEntry.isRowPinned();
             boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
             if (isPinned && !otherPinned) {
@@ -503,31 +745,90 @@
                 return 1;
             }
 
-            return super.compareTo(headsUpEntry);
+            if (mPostTime > headsUpEntry.mPostTime) {
+                return -1;
+            } else if (mPostTime == headsUpEntry.mPostTime) {
+                return mEntry.getKey().compareTo(headsUpEntry.mEntry.getKey());
+            } else {
+                return 1;
+            }
         }
 
         public void setExpanded(boolean expanded) {
             this.mExpanded = expanded;
         }
 
-        @Override
         public void reset() {
-            super.reset();
+            removeAutoRemovalCallbacks("reset()");
+            mEntry = null;
+            mRemoveRunnable = null;
             mExpanded = false;
             mRemoteInputActive = false;
         }
 
-        @Override
+        /**
+         * Clear any pending removal runnables.
+         */
+        public void removeAutoRemovalCallbacks(@Nullable String reason) {
+            final boolean removed = removeAutoRemovalCallbackInternal();
+
+            if (removed) {
+                mLogger.logAutoRemoveCanceled(mEntry, reason);
+            }
+        }
+
+        public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
+            if (mRemoveRunnable == null) {
+                Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+                return;
+            }
+
+            final boolean removed = removeAutoRemovalCallbackInternal();
+
+            if (removed) {
+                mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
+            } else {
+                mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
+            }
+
+            mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+                    delayMillis);
+        }
+
+        public boolean removeAutoRemovalCallbackInternal() {
+            final boolean scheduled = (mCancelRemoveRunnable != null);
+
+            if (scheduled) {
+                mCancelRemoveRunnable.run();
+                mCancelRemoveRunnable = null;
+            }
+
+            return scheduled;
+        }
+
+        /**
+         * Remove the entry at the earliest allowed removal time.
+         */
+        public void removeAsSoonAsPossible() {
+            if (mRemoveRunnable != null) {
+                final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+                scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
+            }
+        }
+
+        /**
+         * Calculate what the post time of a notification is at some current time.
+         * @return the post time
+         */
         protected long calculatePostTime() {
             // The actual post time will be just after the heads-up really slided in
-            return super.calculatePostTime() + mTouchAcceptanceDelay;
+            return mSystemClock.elapsedRealtime() + mTouchAcceptanceDelay;
         }
 
         /**
          * @return When the notification should auto-dismiss itself, based on
          * {@link SystemClock#elapsedRealtime()}
          */
-        @Override
         protected long calculateFinishTime() {
             final long duration = getRecommendedHeadsUpTimeoutMs(
                     isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index d9527fe..b8c7e20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -71,7 +71,7 @@
     fun hasPinnedHeadsUp(): Boolean
 
     /** Returns whether or not the given notification is alerting and managed by this manager. */
-    fun isAlerting(key: String): Boolean
+    fun isHeadsUpEntry(key: String): Boolean
 
     fun isHeadsUpGoingAway(): Boolean
 
@@ -213,7 +213,7 @@
     override fun getTouchableRegion(): Region? = null
     override fun getTopEntry() = null
     override fun hasPinnedHeadsUp() = false
-    override fun isAlerting(key: String) = false
+    override fun isHeadsUpEntry(key: String) = false
     override fun isHeadsUpGoingAway() = false
     override fun isSnoozed(packageName: String) = false
     override fun isSticky(key: String?) = false
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index d8ef981..da6bfe8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -48,10 +48,10 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -76,7 +76,7 @@
 
     protected MockitoSession mStaticMockSession;
 
-    protected final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+    protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     protected @Mock DeviceEntryInteractor mDeviceEntryInteractor;
     protected @Mock LockIconView mLockIconView;
     protected @Mock AnimatedStateListDrawable mIconDrawable;
@@ -175,7 +175,7 @@
                 mPrimaryBouncerInteractor,
                 mContext,
                 () -> mDeviceEntryInteractor,
-                mSceneTestUtils.getFakeSceneContainerFlags()
+                mKosmos.getFakeSceneContainerFlags()
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 132bdb5..b0887ef 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -373,7 +373,7 @@
     @Test
     public void longPress_showBouncer_sceneContainerNotEnabled() {
         init(/* useMigrationFlag= */ false);
-        mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(false);
+        mKosmos.getFakeSceneContainerFlags().setEnabled(false);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
 
         // WHEN longPress
@@ -387,7 +387,7 @@
     @Test
     public void longPress_showBouncer() {
         init(/* useMigrationFlag= */ false);
-        mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true);
+        mKosmos.getFakeSceneContainerFlags().setEnabled(true);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
 
         // WHEN longPress
@@ -401,7 +401,7 @@
     @Test
     public void longPress_falsingTriggered_doesNotShowBouncer() {
         init(/* useMigrationFlag= */ false);
-        mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true);
+        mKosmos.getFakeSceneContainerFlags().setEnabled(true);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
 
         // WHEN longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 68879a5..5e5273b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -55,8 +55,6 @@
 import android.os.UserHandle;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -120,10 +118,6 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
     @Spy
     private SysuiTestableContext mSpyContext = getContext();
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 0ee0939..43f7c60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.biometrics
 
 import android.app.admin.DevicePolicyManager
+import android.content.pm.PackageManager
 import android.hardware.biometrics.BiometricAuthenticator
 import android.hardware.biometrics.BiometricConstants
 import android.hardware.biometrics.BiometricManager
@@ -79,6 +80,8 @@
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
 
+private const val OP_PACKAGE_NAME = "biometric.testapp"
+
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -109,6 +112,8 @@
     lateinit var authController: AuthController
     @Mock
     lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock
+    private lateinit var packageManager: PackageManager
 
     private val testScope = TestScope(StandardTestDispatcher())
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -134,6 +139,7 @@
     private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
 
     private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
+    private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
 
     private var authContainer: TestAuthContainerView? = null
 
@@ -156,6 +162,9 @@
                         selectedUserInteractor,
                         testScope.backgroundScope,
                 )
+        // Set up default logo icon
+        whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+        context.setMockPackageManager(packageManager)
     }
 
     @After
@@ -533,6 +542,7 @@
             mPromptInfo = PromptInfo().apply {
                 this.authenticators = authenticators
             }
+            mOpPackageName = OP_PACKAGE_NAME
         },
         testScope.backgroundScope,
         fingerprintProps,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index ec7ce63..b39e09d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -43,6 +43,7 @@
 
 private const val USER_ID = 9
 private const val CHALLENGE = 90L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -102,7 +103,8 @@
                     PromptInfo().apply { isConfirmationRequested = case },
                     USER_ID,
                     CHALLENGE,
-                    PromptKind.Biometric()
+                    PromptKind.Biometric(),
+                    OP_PACKAGE_NAME
                 )
 
                 assertThat(isConfirmationRequired).isEqualTo(case)
@@ -120,7 +122,8 @@
                     PromptInfo().apply { isConfirmationRequested = case },
                     USER_ID,
                     CHALLENGE,
-                    PromptKind.Biometric()
+                    PromptKind.Biometric(),
+                    OP_PACKAGE_NAME
                 )
 
                 assertThat(isConfirmationRequired).isTrue()
@@ -133,17 +136,19 @@
             val kind = PromptKind.Pin
             val promptInfo = PromptInfo()
 
-            repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind)
+            repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME)
 
             assertThat(repository.kind.value).isEqualTo(kind)
             assertThat(repository.userId.value).isEqualTo(USER_ID)
             assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
             assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+            assertThat(repository.opPackageName.value).isEqualTo(OP_PACKAGE_NAME)
 
             repository.unsetPrompt()
 
             assertThat(repository.promptInfo.value).isNull()
             assertThat(repository.userId.value).isNull()
             assertThat(repository.challenge.value).isNull()
+            assertThat(repository.opPackageName.value).isNull()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index dcefea2..8a46c0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -30,6 +30,7 @@
 
 private const val USER_ID = 22
 private const val OPERATION_ID = 100L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -114,7 +115,8 @@
                 },
                 kind = kind,
                 userId = USER_ID,
-                challenge = OPERATION_ID
+                challenge = OPERATION_ID,
+                opPackageName = OP_PACKAGE_NAME
             )
 
             assertThat(prompt?.title).isEqualTo(title)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index f15b738..52b4275 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -51,6 +51,7 @@
 
 private const val USER_ID = 8
 private const val CHALLENGE = 999L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -113,13 +114,20 @@
 
         assertThat(currentPrompt).isNull()
 
-        interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities)
+        interactor.useBiometricsForAuthentication(
+            info,
+            USER_ID,
+            CHALLENGE,
+            modalities,
+            OP_PACKAGE_NAME
+        )
 
         assertThat(currentPrompt).isNotNull()
         assertThat(currentPrompt?.title).isEqualTo(TITLE)
         assertThat(currentPrompt?.description).isEqualTo(DESCRIPTION)
         assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE)
         assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
+        assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
 
         if (allowCredentialFallback) {
             assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
@@ -167,7 +175,7 @@
 
         assertThat(currentPrompt).isNull()
 
-        interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE)
+        interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME)
 
         // not using biometrics, should be null with no fallback option
         assertThat(currentPrompt).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index a57b890..a46167a42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.biometrics.domain.model
 
+import android.graphics.Bitmap
 import android.hardware.biometrics.PromptContentItemBulletedText
 import android.hardware.biometrics.PromptVerticalListContentView
 import androidx.test.filters.SmallTest
@@ -8,6 +9,7 @@
 import com.android.systemui.biometrics.promptInfo
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -15,6 +17,7 @@
 
 private const val USER_ID = 2
 private const val OPERATION_ID = 8L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
 
 @SmallTest
 @RunWith(JUnit4::class)
@@ -22,6 +25,7 @@
 
     @Test
     fun biometricRequestFromPromptInfo() {
+        val logoRes = R.drawable.ic_cake
         val title = "what"
         val subtitle = "a"
         val description = "request"
@@ -36,6 +40,7 @@
         val request =
             BiometricPromptRequest.Biometric(
                 promptInfo(
+                    logoRes = logoRes,
                     title = title,
                     subtitle = subtitle,
                     description = description,
@@ -44,8 +49,10 @@
                 BiometricUserInfo(USER_ID),
                 BiometricOperationInfo(OPERATION_ID),
                 BiometricModalities(fingerprintProperties = fpPros),
+                OP_PACKAGE_NAME,
             )
 
+        assertThat(request.logoRes).isEqualTo(logoRes)
         assertThat(request.title).isEqualTo(title)
         assertThat(request.subtitle).isEqualTo(subtitle)
         assertThat(request.description).isEqualTo(description)
@@ -57,6 +64,23 @@
     }
 
     @Test
+    fun biometricRequestLogoBitmapFromPromptInfo() {
+        val logoBitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888)
+        val fpPros = fingerprintSensorPropertiesInternal().first()
+        val request =
+            BiometricPromptRequest.Biometric(
+                promptInfo(
+                    logoBitmap = logoBitmap,
+                ),
+                BiometricUserInfo(USER_ID),
+                BiometricOperationInfo(OPERATION_ID),
+                BiometricModalities(fingerprintProperties = fpPros),
+                OP_PACKAGE_NAME,
+            )
+        assertThat(request.logoBitmap).isEqualTo(logoBitmap)
+    }
+
+    @Test
     fun credentialRequestFromPromptInfo() {
         val title = "what"
         val subtitle = "a"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 3944054..3888f2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.content.pm.PackageManager
 import android.content.res.Configuration
+import android.graphics.Bitmap
 import android.graphics.Point
+import android.graphics.drawable.BitmapDrawable
 import android.hardware.biometrics.PromptContentItemBulletedText
 import android.hardware.biometrics.PromptContentView
 import android.hardware.biometrics.PromptInfo
@@ -76,6 +79,7 @@
 private const val USER_ID = 4
 private const val CHALLENGE = 2L
 private const val DELAY = 1000L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -88,9 +92,14 @@
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var udfpsUtils: UdfpsUtils
+    @Mock private lateinit var packageManager: PackageManager
 
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val testScope = TestScope()
+    private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
+    private val logoResFromApp = R.drawable.ic_cake
+    private val logoFromApp = context.getDrawable(logoResFromApp)
+    private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
 
     private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
     private lateinit var promptRepository: FakePromptRepository
@@ -153,6 +162,12 @@
                 udfpsUtils
             )
         iconViewModel = viewModel.iconViewModel
+
+        // Set up default logo icon and app customized icon
+        whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+        context.setMockPackageManager(packageManager)
+        val resources = context.getOrCreateTestableResources()
+        resources.addOverride(logoResFromApp, logoFromApp)
     }
 
     @Test
@@ -1227,6 +1242,26 @@
             assertThat(contentView).isNull()
         }
 
+    @Test
+    fun defaultLogoIfNoLogoSet() = runGenericTest {
+        val logo by collectLastValue(viewModel.logo)
+        assertThat(logo).isEqualTo(defaultLogoIcon)
+    }
+
+    @Test
+    fun logoResSetByApp() =
+        runGenericTest(logoRes = logoResFromApp) {
+            val logo by collectLastValue(viewModel.logo)
+            assertThat(logo).isEqualTo(logoFromApp)
+        }
+
+    @Test
+    fun logoBitmapSetByApp() =
+        runGenericTest(logoBitmap = logoBitmapFromApp) {
+            val logo by collectLastValue(viewModel.logo)
+            assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
+        }
+
     /** Asserts that the selected buttons are visible now. */
     private suspend fun TestScope.assertButtonsVisible(
         tryAgain: Boolean = false,
@@ -1248,6 +1283,8 @@
         allowCredentialFallback: Boolean = false,
         description: String? = null,
         contentView: PromptContentView? = null,
+        logoRes: Int = -1,
+        logoBitmap: Bitmap? = null,
         block: suspend TestScope.() -> Unit
     ) {
         selector.initializePrompt(
@@ -1257,6 +1294,8 @@
             face = testCase.face,
             descriptionFromApp = description,
             contentViewFromApp = contentView,
+            logoResFromApp = logoRes,
+            logoBitmapFromApp = logoBitmap,
         )
 
         // put the view model in the initial authenticating state, unless explicitly skipped
@@ -1434,9 +1473,13 @@
     allowCredentialFallback: Boolean = false,
     descriptionFromApp: String? = null,
     contentViewFromApp: PromptContentView? = null,
+    logoResFromApp: Int = -1,
+    logoBitmapFromApp: Bitmap? = null,
 ) {
     val info =
         PromptInfo().apply {
+            logoRes = logoResFromApp
+            logoBitmap = logoBitmapFromApp
             title = "t"
             subtitle = "s"
             description = descriptionFromApp
@@ -1445,11 +1488,13 @@
             isDeviceCredentialAllowed = allowCredentialFallback
             isConfirmationRequested = requireConfirmation
         }
+
     useBiometricsForAuthentication(
         info,
         USER_ID,
         CHALLENGE,
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
+        OP_PACKAGE_NAME,
     )
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
index 44c57f3..134c40d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -3,8 +3,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.SystemClock
@@ -23,8 +24,8 @@
     @Mock private lateinit var systemClock: SystemClock
     @Mock private lateinit var bouncerLogger: TableLogBuffer
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     lateinit var underTest: KeyguardBouncerRepository
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
deleted file mode 100644
index 30a5497..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.communal.data.repository
-
-import android.content.pm.UserInfo
-import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.FakeLogBuffer
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
-    private lateinit var secureSettings: FakeSettings
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var userTracker: FakeUserTracker
-    private lateinit var logBuffer: LogBuffer
-
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        logBuffer = FakeLogBuffer.Factory.create()
-        secureSettings = FakeSettings()
-        userRepository = FakeUserRepository()
-        val listOfUserInfo = listOf(MAIN_USER_INFO)
-        userRepository.setUserInfos(listOfUserInfo)
-
-        userTracker = FakeUserTracker()
-        userTracker.set(
-            userInfos = listOfUserInfo,
-            selectedUserIndex = 0,
-        )
-    }
-
-    @Test
-    fun tutorialSettingState_defaultToNotStarted() =
-        testScope.runTest {
-            val repository = initCommunalTutorialRepository()
-            val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
-            assertThat(tutorialSettingState)
-                .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
-        }
-
-    @Test
-    fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() =
-        testScope.runTest {
-            val repository = initCommunalTutorialRepository()
-            setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
-            val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
-            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
-        }
-
-    @Test
-    fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() =
-        testScope.runTest {
-            val repository = initCommunalTutorialRepository()
-            setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
-            val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
-            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
-        }
-
-    private fun initCommunalTutorialRepository(): CommunalTutorialRepositoryImpl {
-        return CommunalTutorialRepositoryImpl(
-            testScope.backgroundScope,
-            testDispatcher,
-            userRepository,
-            secureSettings,
-            userTracker,
-            logBuffer
-        )
-    }
-
-    private fun setTutorialStateSetting(
-        @Settings.Secure.HubModeTutorialState state: Int,
-        user: UserInfo = MAIN_USER_INFO
-    ) {
-        secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id)
-    }
-
-    companion object {
-        private val MAIN_USER_INFO =
-            UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 73885f4..6be9275 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -104,18 +104,19 @@
     private lateinit var dreamOverlayCallback:
         ArgumentCaptor<(DreamOverlayStateController.Callback)>
     @JvmField @Rule val mockito = MockitoJUnit.rule()
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
     private lateinit var mediaHierarchyManager: MediaHierarchyManager
     private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
     private lateinit var mediaFrame: ViewGroup
     private val configurationController = FakeConfigurationController()
-    private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true)
+    private val communalRepository =
+        FakeCommunalRepository(applicationScope = testScope.backgroundScope)
     private val communalInteractor =
         CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor
     private val settings = FakeSettings()
     private lateinit var testableLooper: TestableLooper
     private lateinit var fakeHandler: FakeHandler
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
 
     @Before
     fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index c711806..1cb3bf6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -393,23 +393,23 @@
     }
 
     @Test
-    fun backgroundAlpha_inSplitShade_followsExpansion_with_0_99_delay() {
+    fun backgroundAlpha_inSplitShade_followsExpansion_with_0_15_delay() {
         val underTest = utils.footerActionsViewModel()
         val floatTolerance = 0.01f
 
         underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true)
         assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
 
-        underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true)
+        underTest.onQuickSettingsExpansionChanged(0.1f, isInSplitShade = true)
         assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
 
-        underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = true)
+        underTest.onQuickSettingsExpansionChanged(0.14f, isInSplitShade = true)
         assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
 
-        underTest.onQuickSettingsExpansionChanged(0.991f, isInSplitShade = true)
+        underTest.onQuickSettingsExpansionChanged(0.235f, isInSplitShade = true)
         assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.1f)
 
-        underTest.onQuickSettingsExpansionChanged(0.995f, isInSplitShade = true)
+        underTest.onQuickSettingsExpansionChanged(0.575f, isInSplitShade = true)
         assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.5f)
 
         underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
index 33066d2..9563ceb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
@@ -162,13 +162,15 @@
 
     @Test
     fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
-        `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
-        bluetoothTileDialogViewModel.showDialog(context, null)
+        testScope.runTest {
+            `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+            bluetoothTileDialogViewModel.showDialog(context, null)
 
-        val clickedView = View(context)
-        bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
+            val clickedView = View(context)
+            bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
 
-        verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
-        verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
+            verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
+            verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 49579f6..b3e386e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -113,6 +113,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -125,7 +126,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
@@ -355,8 +355,8 @@
     protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected KeyguardInteractor mKeyguardInteractor;
     protected ShadeAnimationInteractor mShadeAnimationInteractor;
-    protected SceneTestUtils mUtils = new SceneTestUtils(this);
-    protected TestScope mTestScope = mUtils.getTestScope();
+    protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    protected TestScope mTestScope = mKosmos.getTestScope();
     protected ShadeInteractor mShadeInteractor;
     protected PowerInteractor mPowerInteractor;
     protected NotificationPanelViewController.TouchHandler mTouchHandler;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 971c8a3..a894f87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -73,11 +73,11 @@
 import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
 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.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -150,8 +150,8 @@
 
     private final Executor mMainExecutor = MoreExecutors.directExecutor();
     private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
-    private final SceneTestUtils mUtils = new SceneTestUtils(this);
-    private final TestScope mTestScope = mUtils.getTestScope();
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    private final TestScope mTestScope = mKosmos.getTestScope();
     private ShadeInteractor mShadeInteractor;
 
     private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
@@ -178,15 +178,15 @@
         FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
         FakeShadeRepository shadeRepository = new FakeShadeRepository();
 
-        mScreenOffAnimationController = mUtils.getScreenOffAnimationController();
-        mStatusBarStateController = spy(mUtils.getStatusBarStateController());
-        PowerInteractor powerInteractor = mUtils.powerInteractor();
+        mScreenOffAnimationController = mKosmos.getScreenOffAnimationController();
+        mStatusBarStateController = spy(mKosmos.getStatusBarStateController());
+        PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
 
         SceneInteractor sceneInteractor = new SceneInteractor(
                 mTestScope.getBackgroundScope(),
                 new SceneContainerRepository(
                         mTestScope.getBackgroundScope(),
-                        mUtils.fakeSceneContainerConfig()),
+                        mKosmos.getFakeSceneContainerConfig()),
                 powerInteractor,
                 mock(SceneLogger.class));
 
@@ -219,8 +219,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
-                mUtils.getTestDispatcher(),
-                mUtils.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 shadeRepository,
@@ -245,8 +245,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
-                mUtils.getTestDispatcher(),
-                mUtils.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 mKeyguardSecurityModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index ca68fd8..cbd4d2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -61,6 +61,7 @@
 import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
 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.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -68,7 +69,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -131,8 +131,8 @@
 
     protected QuickSettingsController mQsController;
 
-    protected SceneTestUtils mUtils = new SceneTestUtils(this);
-    protected TestScope mTestScope = mUtils.getTestScope();
+    protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    protected TestScope mTestScope = mKosmos.getTestScope();
 
     @Mock protected Resources mResources;
     @Mock protected KeyguardBottomAreaView mQsFrame;
@@ -203,8 +203,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
-        mStatusBarStateController = mUtils.getStatusBarStateController();
-        mInteractionJankMonitor = mUtils.getInteractionJankMonitor();
+        mStatusBarStateController = mKosmos.getStatusBarStateController();
+        mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
 
         FakeDeviceProvisioningRepository deviceProvisioningRepository =
                 new FakeDeviceProvisioningRepository();
@@ -212,13 +212,13 @@
         FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
         FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
 
-        PowerInteractor powerInteractor = mUtils.powerInteractor();
+        PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
 
         SceneInteractor sceneInteractor = new SceneInteractor(
                 mTestScope.getBackgroundScope(),
                 new SceneContainerRepository(
                         mTestScope.getBackgroundScope(),
-                        mUtils.fakeSceneContainerConfig()),
+                        mKosmos.getFakeSceneContainerConfig()),
                 powerInteractor,
                 mock(SceneLogger.class));
 
@@ -250,8 +250,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
-                mUtils.getTestDispatcher(),
-                mUtils.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 mShadeRepository,
@@ -276,8 +276,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
-                mUtils.getTestDispatcher(),
-                mUtils.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 mock(KeyguardSecurityModel.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
deleted file mode 100644
index e1d9282..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.systemui.statusbar;
-
-import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.spy;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeGlobalSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.time.SystemClock;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
[email protected]
-public class AlertingNotificationManagerTest extends SysuiTestCase {
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-
-    private static final String TEST_PACKAGE_NAME = "test";
-    private static final int TEST_UID = 0;
-
-    protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
-    protected static final int TEST_AUTO_DISMISS_TIME = 600;
-    protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
-    // Number of notifications to use in tests requiring multiple notifications
-    private static final int TEST_NUM_NOTIFICATIONS = 4;
-
-    protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
-    protected final FakeSystemClock mSystemClock = new FakeSystemClock();
-    protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
-
-    @Mock protected ExpandableNotificationRow mRow;
-
-    static {
-        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
-        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
-    }
-
-    private static class TestableAlertingNotificationManager extends AlertingNotificationManager {
-        private AlertEntry mLastCreatedEntry;
-
-        private TestableAlertingNotificationManager(SystemClock systemClock,
-                DelayableExecutor executor) {
-            super(new HeadsUpManagerLogger(logcatLogBuffer()), systemClock, executor);
-            mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
-            mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
-        }
-
-        @Override
-        protected void onAlertEntryAdded(AlertEntry alertEntry) {}
-
-        @Override
-        protected void onAlertEntryRemoved(AlertEntry alertEntry) {}
-
-        @Override
-        protected AlertEntry createAlertEntry() {
-            mLastCreatedEntry = spy(super.createAlertEntry());
-            return mLastCreatedEntry;
-        }
-
-        @Override
-        public int getContentFlag() {
-            return FLAG_CONTENT_VIEW_CONTRACTED;
-        }
-    }
-
-    protected AlertingNotificationManager createAlertingNotificationManager() {
-        return new TestableAlertingNotificationManager(mSystemClock, mExecutor);
-    }
-
-    protected StatusBarNotification createSbn(int id, Notification n) {
-        return new StatusBarNotification(
-                TEST_PACKAGE_NAME /* pkg */,
-                TEST_PACKAGE_NAME,
-                id,
-                null /* tag */,
-                TEST_UID,
-                0 /* initialPid */,
-                n,
-                new UserHandle(ActivityManager.getCurrentUser()),
-                null /* overrideGroupKey */,
-                0 /* postTime */);
-    }
-
-    protected StatusBarNotification createSbn(int id, Notification.Builder n) {
-        return createSbn(id, n.build());
-    }
-
-    protected StatusBarNotification createSbn(int id) {
-        final Notification.Builder b = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-        return createSbn(id, b);
-    }
-
-    protected NotificationEntry createEntry(int id, Notification n) {
-        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
-    }
-
-    protected NotificationEntry createEntry(int id) {
-        return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
-    }
-
-
-    @Test
-    public void testShowNotification_addsEntry() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
-
-        alm.showNotification(entry);
-
-        assertTrue(alm.isAlerting(entry.getKey()));
-        assertTrue(alm.hasNotifications());
-        assertEquals(entry, alm.getEntry(entry.getKey()));
-    }
-
-    @Test
-    public void testShowNotification_autoDismisses() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
-
-        alm.showNotification(entry);
-        mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
-
-        assertFalse(alm.isAlerting(entry.getKey()));
-    }
-
-    @Test
-    public void testRemoveNotification_removeDeferred() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
-
-        alm.showNotification(entry);
-
-        final boolean removedImmediately = alm.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ false);
-        assertFalse(removedImmediately);
-        assertTrue(alm.isAlerting(entry.getKey()));
-    }
-
-    @Test
-    public void testRemoveNotification_forceRemove() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
-
-        alm.showNotification(entry);
-
-        final boolean removedImmediately = alm.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ true);
-        assertTrue(removedImmediately);
-        assertFalse(alm.isAlerting(entry.getKey()));
-    }
-
-    @Test
-    public void testReleaseAllImmediately() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
-            final NotificationEntry entry = createEntry(i);
-            entry.setRow(mRow);
-            alm.showNotification(entry);
-        }
-
-        alm.releaseAllImmediately();
-
-        assertEquals(0, alm.getAllEntries().count());
-    }
-
-    @Test
-    public void testCanRemoveImmediately_notShownLongEnough() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
-
-        alm.showNotification(entry);
-
-        // The entry has just been added so we should not remove immediately.
-        assertFalse(alm.canRemoveImmediately(entry.getKey()));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 4a365b9..05e866e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -41,10 +41,12 @@
 import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -56,6 +58,7 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.flow.emptyFlow
 import org.junit.Assert.assertEquals
@@ -71,17 +74,17 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class StatusBarStateControllerImplTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val testDispatcher = utils.testDispatcher
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val testDispatcher = kosmos.testDispatcher
     private lateinit var shadeInteractor: ShadeInteractor
     private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
     private lateinit var fromPrimaryBouncerTransitionInteractor:
@@ -131,7 +134,7 @@
                 FakeKeyguardBouncerRepository(),
                 ConfigurationInteractor(configurationRepository),
                 shadeRepository,
-                utils::sceneInteractor
+                { kosmos.sceneInteractor },
             )
         val keyguardTransitionInteractor =
             KeyguardTransitionInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index 6059363..cd74410 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -73,7 +73,7 @@
     }
 
     private fun flagNotificationAsHun() {
-        `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(true)
+        `when`(headsUpManager.isHeadsUpEntry(notificationKey)).thenReturn(true)
     }
 
     @Test
@@ -151,8 +151,8 @@
             .build()
         assertSame(summary, notification.entry.parent?.summary)
 
-        `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(false)
-        `when`(headsUpManager.isAlerting(summary.key)).thenReturn(true)
+        `when`(headsUpManager.isHeadsUpEntry(notificationKey)).thenReturn(false)
+        `when`(headsUpManager.isHeadsUpEntry(summary.key)).thenReturn(true)
 
         assertNotSame(GROUP_ALERT_SUMMARY, summary.sbn.notification.groupAlertBehavior)
         assertNotSame(GROUP_ALERT_SUMMARY, notification.entry.sbn.notification.groupAlertBehavior)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
similarity index 100%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
similarity index 90%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index 4efcada..dda7fad 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -52,38 +52,32 @@
         return ge;
     }
 
-    /** Sets the group key. */
     public GroupEntryBuilder setKey(String key) {
         mKey = key;
         return this;
     }
 
-    /** Sets the creation time. */
     public GroupEntryBuilder setCreationTime(long creationTime) {
         mCreationTime = creationTime;
         return this;
     }
 
-    /** Sets the parent entry of the group. */
     public GroupEntryBuilder setParent(@Nullable GroupEntry entry) {
         mParent = entry;
         return this;
     }
 
-    /** Sets the section the group belongs to. */
     public GroupEntryBuilder setSection(@Nullable NotifSection section) {
         mNotifSection = section;
         return this;
     }
 
-    /** Sets the group summary. */
     public GroupEntryBuilder setSummary(
             NotificationEntry summary) {
         mSummary = summary;
         return this;
     }
 
-    /** Sets the group children. */
     public GroupEntryBuilder setChildren(List<NotificationEntry> children) {
         mChildren.clear();
         mChildren.addAll(children);
@@ -96,7 +90,6 @@
         return this;
     }
 
-    /** Get the group's internal children list. */
     public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) {
         return groupEntry.getRawChildren();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 36f643a..65697b73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -24,6 +24,7 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -52,8 +53,8 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -77,22 +78,28 @@
 
     private lateinit var coordinator: ConversationCoordinator
 
+    private val featureFlags = FakeFeatureFlagsClassic()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        coordinator =
-            ConversationCoordinator(
+        coordinator = ConversationCoordinator(
+            peopleNotificationIdentifier,
+            conversationIconManager,
+            HighPriorityProvider(
                 peopleNotificationIdentifier,
-                conversationIconManager,
-                HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
-                headerController
-            )
+                GroupMembershipManagerImpl(featureFlags)
+            ),
+            headerController
+        )
         whenever(channel.isImportantConversation).thenReturn(true)
 
         coordinator.attach(pipeline)
 
         // capture arguments:
-        promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) }
+        promoter = withArgCaptor {
+            verify(pipeline).addPromoter(capture())
+        }
         beforeRenderListListener = withArgCaptor {
             verify(pipeline).addOnBeforeRenderListListener(capture())
         }
@@ -104,10 +111,10 @@
         entry = NotificationEntryBuilder().setChannel(channel).build()
 
         val section = NotifSection(peopleAlertingSectioner, 0)
-        entryA =
-            NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build()
-        entryB =
-            NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build()
+        entryA = NotificationEntryBuilder().setChannel(channel)
+            .setSection(section).setTag("A").build()
+        entryB = NotificationEntryBuilder().setChannel(channel)
+            .setSection(section).setTag("B").build()
     }
 
     @Test
@@ -122,12 +129,11 @@
         val altChildA = NotificationEntryBuilder().setTag("A").build()
         val altChildB = NotificationEntryBuilder().setTag("B").build()
         val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
-        val groupEntry =
-            GroupEntryBuilder()
-                .setParent(GroupEntry.ROOT_ENTRY)
-                .setSummary(summary)
-                .setChildren(listOf(entry, altChildA, altChildB))
-                .build()
+        val groupEntry = GroupEntryBuilder()
+            .setParent(GroupEntry.ROOT_ENTRY)
+            .setSummary(summary)
+            .setChildren(listOf(entry, altChildA, altChildB))
+            .build()
         assertTrue(promoter.shouldPromoteToTopLevel(entry))
         assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
         assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
@@ -140,42 +146,41 @@
     @Test
     fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
         // GIVEN
-        val alertingEntry =
-            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build()
+        val alertingEntry = NotificationEntryBuilder().setChannel(channel)
+                .setImportance(IMPORTANCE_DEFAULT).build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
-            .thenReturn(TYPE_PERSON)
+                .thenReturn(TYPE_PERSON)
 
         // put alerting people notifications in this section
         assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
-    }
+       }
 
     @Test
     fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
         // GIVEN
-        val silentEntry =
-            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
+        val silentEntry = NotificationEntryBuilder().setChannel(channel)
+                .setImportance(IMPORTANCE_LOW).build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
-            .thenReturn(TYPE_PERSON)
+                .thenReturn(TYPE_PERSON)
 
         // THEN put silent people notifications in this section
         assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
         // People Alerting sectioning happens before the silent one.
-        // It claims high important conversations and rest of conversations will be considered as
-        // silent.
+        // It claims high important conversations and rest of conversations will be considered as silent.
         assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse()
     }
 
     @Test
     fun testNotInPeopleSection() {
         // GIVEN
-        val entry =
-            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
-        val importantEntry =
-            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build()
+        val entry = NotificationEntryBuilder().setChannel(channel)
+                .setImportance(IMPORTANCE_LOW).build()
+        val importantEntry = NotificationEntryBuilder().setChannel(channel)
+                .setImportance(IMPORTANCE_HIGH).build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
-            .thenReturn(TYPE_NON_PERSON)
+                .thenReturn(TYPE_NON_PERSON)
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
-            .thenReturn(TYPE_NON_PERSON)
+                .thenReturn(TYPE_NON_PERSON)
 
         // THEN - only put people notification either silent or alerting
         assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
@@ -185,23 +190,19 @@
     @Test
     fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() {
         // GIVEN
-        val altChildA =
-            NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build()
-        val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build()
-        val summary =
-            NotificationEntryBuilder()
-                .setId(2)
-                .setImportance(IMPORTANCE_LOW)
-                .setChannel(channel)
-                .build()
-        val groupEntry =
-            GroupEntryBuilder()
+        val altChildA = NotificationEntryBuilder().setTag("A")
+                .setImportance(IMPORTANCE_DEFAULT).build()
+        val altChildB = NotificationEntryBuilder().setTag("B")
+                .setImportance(IMPORTANCE_LOW).build()
+        val summary = NotificationEntryBuilder().setId(2)
+                .setImportance(IMPORTANCE_LOW).setChannel(channel).build()
+        val groupEntry = GroupEntryBuilder()
                 .setParent(GroupEntry.ROOT_ENTRY)
                 .setSummary(summary)
                 .setChildren(listOf(altChildA, altChildB))
                 .build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
-            .thenReturn(TYPE_PERSON)
+                .thenReturn(TYPE_PERSON)
         // THEN
         assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index a8be62b..cd75e08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -148,7 +148,7 @@
             verify(remoteInputManager).addActionPressListener(capture())
         }
         given(headsUpManager.allEntries).willAnswer { huns.stream() }
-        given(headsUpManager.isAlerting(anyString())).willAnswer { invocation ->
+        given(headsUpManager.isHeadsUpEntry(anyString())).willAnswer { invocation ->
             val key = invocation.getArgument<String>(0)
             huns.any { entry -> entry.key == key }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 2e74d11..ea5a6e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -140,7 +140,7 @@
                 .setSummary(mEntry)
                 .build();
 
-        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(false);
+        when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(false);
 
         // Whenever we invalidate, the pipeline runs again, so we invalidate the state
         doAnswer(i -> {
@@ -373,7 +373,7 @@
         setSleepy(false);
 
         // WHEN a notification is alerting and visible
-        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+        when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
         when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
                 .thenReturn(true);
 
@@ -389,7 +389,7 @@
         setSleepy(false);
 
         // WHEN a notification is alerting but not visible
-        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+        when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
         when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
                 .thenReturn(false);
 
@@ -537,7 +537,7 @@
         assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
 
         // GIVEN mEntry is a HUN
-        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+        when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
 
         // THEN group + section changes are allowed
         assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
similarity index 69%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 0c7ce97..0a10b2c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -16,10 +16,11 @@
 
 package com.android.systemui.statusbar.notification.collection.render
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -33,19 +34,18 @@
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
-import org.junit.runner.RunWith
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
 class GroupExpansionManagerTest : SysuiTestCase() {
-    private lateinit var underTest: GroupExpansionManagerImpl
+    private lateinit var gem: GroupExpansionManagerImpl
 
     private val dumpManager: DumpManager = mock()
     private val groupMembershipManager: GroupMembershipManager = mock()
+    private val featureFlags = FakeFeatureFlagsClassic()
 
     private val pipeline: NotifPipeline = mock()
     private lateinit var beforeRenderListListener: OnBeforeRenderListListener
@@ -85,57 +85,79 @@
         whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
         whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
 
-        underTest = GroupExpansionManagerImpl(dumpManager, groupMembershipManager)
+        gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags)
     }
 
     @Test
-    fun notifyOnlyOnChange() {
-        var listenerCalledCount = 0
-        underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
+    fun testNotifyOnlyOnChange_enabled() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
 
-        underTest.setGroupExpanded(summary1, false)
+        var listenerCalledCount = 0
+        gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
+
+        gem.setGroupExpanded(summary1, false)
         assertThat(listenerCalledCount).isEqualTo(0)
-        underTest.setGroupExpanded(summary1, true)
+        gem.setGroupExpanded(summary1, true)
         assertThat(listenerCalledCount).isEqualTo(1)
-        underTest.setGroupExpanded(summary2, true)
+        gem.setGroupExpanded(summary2, true)
         assertThat(listenerCalledCount).isEqualTo(2)
-        underTest.setGroupExpanded(summary1, true)
+        gem.setGroupExpanded(summary1, true)
         assertThat(listenerCalledCount).isEqualTo(2)
-        underTest.setGroupExpanded(summary2, false)
+        gem.setGroupExpanded(summary2, false)
         assertThat(listenerCalledCount).isEqualTo(3)
     }
 
     @Test
-    fun expandUnattachedEntry() {
-        // First, expand the entry when it is attached.
-        underTest.setGroupExpanded(summary1, true)
-        assertThat(underTest.isGroupExpanded(summary1)).isTrue()
+    fun testNotifyOnlyOnChange_disabled() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
 
-        // Un-attach it, and un-expand it.
-        NotificationEntryBuilder.setNewParent(summary1, null)
-        underTest.setGroupExpanded(summary1, false)
+        var listenerCalledCount = 0
+        gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
 
-        // Expanding again should throw.
-        assertThrows(IllegalArgumentException::class.java) {
-            underTest.setGroupExpanded(summary1, true)
-        }
+        gem.setGroupExpanded(summary1, false)
+        assertThat(listenerCalledCount).isEqualTo(1)
+        gem.setGroupExpanded(summary1, true)
+        assertThat(listenerCalledCount).isEqualTo(2)
+        gem.setGroupExpanded(summary2, true)
+        assertThat(listenerCalledCount).isEqualTo(3)
+        gem.setGroupExpanded(summary1, true)
+        assertThat(listenerCalledCount).isEqualTo(4)
+        gem.setGroupExpanded(summary2, false)
+        assertThat(listenerCalledCount).isEqualTo(5)
     }
 
     @Test
-    fun syncWithPipeline() {
-        underTest.attach(pipeline)
+    fun testExpandUnattachedEntry() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        // First, expand the entry when it is attached.
+        gem.setGroupExpanded(summary1, true)
+        assertThat(gem.isGroupExpanded(summary1)).isTrue()
+
+        // Un-attach it, and un-expand it.
+        NotificationEntryBuilder.setNewParent(summary1, null)
+        gem.setGroupExpanded(summary1, false)
+
+        // Expanding again should throw.
+        assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) }
+    }
+
+    @Test
+    fun testSyncWithPipeline() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+        gem.attach(pipeline)
         beforeRenderListListener = withArgCaptor {
             verify(pipeline).addOnBeforeRenderListListener(capture())
         }
 
         val listener: OnGroupExpansionChangeListener = mock()
-        underTest.registerGroupExpansionChangeListener(listener)
+        gem.registerGroupExpansionChangeListener(listener)
 
         beforeRenderListListener.onBeforeRenderList(entries)
         verify(listener, never()).onGroupExpansionChange(any(), any())
 
         // Expand one of the groups.
-        underTest.setGroupExpanded(summary1, true)
+        gem.setGroupExpanded(summary1, true)
         verify(listener).onGroupExpansionChange(summary1.row, true)
 
         // Empty the pipeline list and verify that the group is no longer expanded.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
new file mode 100644
index 0000000..c1ffa64
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class GroupMembershipManagerTest : SysuiTestCase() {
+    private lateinit var gmm: GroupMembershipManagerImpl
+
+    private val featureFlags = FakeFeatureFlagsClassic()
+
+    @Before
+    fun setUp() {
+        gmm = GroupMembershipManagerImpl(featureFlags)
+    }
+
+    @Test
+    fun testIsChildInGroup_topLevel() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
+        val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+        assertThat(gmm.isChildInGroup(topLevelEntry)).isFalse()
+    }
+
+    @Test
+    fun testIsChildInGroup_noParent_old() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
+        val noParentEntry = NotificationEntryBuilder().setParent(null).build()
+        assertThat(gmm.isChildInGroup(noParentEntry)).isTrue()
+    }
+
+    @Test
+    fun testIsChildInGroup_noParent_new() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+        val noParentEntry = NotificationEntryBuilder().setParent(null).build()
+        assertThat(gmm.isChildInGroup(noParentEntry)).isFalse()
+    }
+    @Test
+    fun testIsChildInGroup_summary_old() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(gmm.isChildInGroup(summary)).isTrue()
+    }
+
+    @Test
+    fun testIsChildInGroup_summary_new() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(gmm.isChildInGroup(summary)).isFalse()
+    }
+
+    @Test
+    fun testIsChildInGroup_child() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
+        val childEntry = NotificationEntryBuilder().build()
+        assertThat(gmm.isChildInGroup(childEntry)).isTrue()
+    }
+
+    @Test
+    fun testIsGroupSummary_topLevelEntry() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+        assertThat(gmm.isGroupSummary(entry)).isFalse()
+    }
+
+    @Test
+    fun testIsGroupSummary_summary() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(gmm.isGroupSummary(summary)).isTrue()
+    }
+
+    @Test
+    fun testIsGroupSummary_child() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+        assertThat(gmm.isGroupSummary(entry)).isFalse()
+    }
+
+    @Test
+    fun testGetGroupSummary_topLevelEntry() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+        assertThat(gmm.getGroupSummary(entry)).isNull()
+    }
+
+    @Test
+    fun testGetGroupSummary_summary() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary)
+    }
+
+    @Test
+    fun testGetGroupSummary_child() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+        assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 2b1f5fc..c350de2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -35,13 +35,12 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.BaseHeadsUpManagerTest;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -64,7 +63,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
+public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
     private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
@@ -137,11 +136,6 @@
         );
     }
 
-    @Override
-    protected AlertingNotificationManager createAlertingNotificationManager() {
-        return createHeadsUpManagerPhone();
-    }
-
     @Before
     public void setUp() {
         when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
@@ -179,7 +173,7 @@
                 /* releaseImmediately = */ false);
 
         assertTrue(removedImmediately);
-        assertFalse(hmp.isAlerting(entry.getKey()));
+        assertFalse(hmp.isHeadsUpEntry(entry.getKey()));
     }
 
     @Test
@@ -218,6 +212,6 @@
         hmp.extendHeadsUp();
         mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2);
 
-        assertTrue(hmp.isAlerting(entry.getKey()));
+        assertTrue(hmp.isHeadsUpEntry(entry.getKey()));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 91cbc32..7362e34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -24,9 +24,9 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.policy.DevicePostureController
@@ -35,6 +35,7 @@
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.tuner.TunerService
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,8 +61,8 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardBypassControllerTest : SysuiTestCase() {
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private val featureFlags = FakeFeatureFlags()
     private val shadeRepository = FakeShadeRepository()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 5fa7f13e..2d120cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -60,10 +60,10 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.statusbar.CommandQueue;
@@ -149,7 +149,7 @@
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private final TestScope mTestScope = TestScopeProvider.getTestScope();
     private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
-    private final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     private KeyguardInteractor mKeyguardInteractor;
     private KeyguardStatusBarViewModel mViewModel;
 
@@ -166,11 +166,11 @@
                 mKeyguardRepository,
                 mCommandQueue,
                 PowerInteractorFactory.create().getPowerInteractor(),
-                mSceneTestUtils.getFakeSceneContainerFlags(),
+                mKosmos.getFakeSceneContainerFlags(),
                 new FakeKeyguardBouncerRepository(),
                 new ConfigurationInteractor(new FakeConfigurationRepository()),
                 new FakeShadeRepository(),
-                () -> mSceneTestUtils.sceneInteractor());
+                () -> mKosmos.getSceneInteractor());
         mViewModel =
                 new KeyguardStatusBarViewModel(
                         mTestScope.getBackgroundScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 6a3b2c3..4c824c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -19,6 +19,7 @@
 import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
 
 import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -36,12 +37,15 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Region;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -51,13 +55,16 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
-import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.FakeGlobalSettings;
 import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
 
 import org.junit.Rule;
@@ -70,10 +77,12 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
+public class BaseHeadsUpManagerTest extends SysuiTestCase {
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
 
+    private static final String TEST_PACKAGE_NAME = "BaseHeadsUpManagerTest";
+
     private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
     private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
 
@@ -81,6 +90,20 @@
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
+    private static final int TEST_UID = 0;
+
+    protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
+    protected static final int TEST_AUTO_DISMISS_TIME = 600;
+    protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
+    // Number of notifications to use in tests requiring multiple notifications
+    private static final int TEST_NUM_NOTIFICATIONS = 4;
+
+    protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+    protected final FakeSystemClock mSystemClock = new FakeSystemClock();
+    protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
+
+    @Mock protected ExpandableNotificationRow mRow;
+
     static {
         assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
         assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
@@ -88,6 +111,9 @@
     }
 
     private final class TestableHeadsUpManager extends BaseHeadsUpManager {
+
+        private HeadsUpEntry mLastCreatedEntry;
+
         TestableHeadsUpManager(Context context,
                 HeadsUpManagerLogger logger,
                 DelayableExecutor executor,
@@ -97,10 +123,23 @@
                 UiEventLogger uiEventLogger) {
             super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
                     executor, accessibilityManagerWrapper, uiEventLogger);
+
             mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
             mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
+
+        }
+
+        @Override
+        protected HeadsUpEntry createHeadsUpEntry() {
+            mLastCreatedEntry = spy(super.createHeadsUpEntry());
+            return mLastCreatedEntry;
+        }
+
+        @Override
+        public int getContentFlag() {
+            return FLAG_CONTENT_VIEW_CONTRACTED;
         }
 
         // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
@@ -173,16 +212,46 @@
         }
     }
 
+    protected StatusBarNotification createSbn(int id, Notification n) {
+        return new StatusBarNotification(
+                TEST_PACKAGE_NAME /* pkg */,
+                TEST_PACKAGE_NAME,
+                id,
+                null /* tag */,
+                TEST_UID,
+                0 /* initialPid */,
+                n,
+                new UserHandle(ActivityManager.getCurrentUser()),
+                null /* overrideGroupKey */,
+                0 /* postTime */);
+    }
+
+    protected StatusBarNotification createSbn(int id, Notification.Builder n) {
+        return createSbn(id, n.build());
+    }
+
+    protected StatusBarNotification createSbn(int id) {
+        final Notification.Builder b = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+        return createSbn(id, b);
+    }
+
+    protected NotificationEntry createEntry(int id, Notification n) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+    }
+
+    protected NotificationEntry createEntry(int id) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
+    }
+
+
     private BaseHeadsUpManager createHeadsUpManager() {
         return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
                 mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
     }
 
-    @Override
-    protected AlertingNotificationManager createAlertingNotificationManager() {
-        return createHeadsUpManager();
-    }
-
     private NotificationEntry createStickyEntry(int id) {
         final Notification notif = new Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
@@ -224,6 +293,79 @@
         }
     }
 
+    @Test
+    public void testShowNotification_addsEntry() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
+
+        assertTrue(alm.isHeadsUpEntry(entry.getKey()));
+        assertTrue(alm.hasNotifications());
+        assertEquals(entry, alm.getEntry(entry.getKey()));
+    }
+
+    @Test
+    public void testShowNotification_autoDismisses() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
+        mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
+
+        assertFalse(alm.isHeadsUpEntry(entry.getKey()));
+    }
+
+    @Test
+    public void testRemoveNotification_removeDeferred() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
+
+        final boolean removedImmediately = alm.removeNotification(
+                entry.getKey(), /* releaseImmediately = */ false);
+        assertFalse(removedImmediately);
+        assertTrue(alm.isHeadsUpEntry(entry.getKey()));
+    }
+
+    @Test
+    public void testRemoveNotification_forceRemove() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
+
+        final boolean removedImmediately = alm.removeNotification(
+                entry.getKey(), /* releaseImmediately = */ true);
+        assertTrue(removedImmediately);
+        assertFalse(alm.isHeadsUpEntry(entry.getKey()));
+    }
+
+    @Test
+    public void testReleaseAllImmediately() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
+            final NotificationEntry entry = createEntry(i);
+            entry.setRow(mRow);
+            alm.showNotification(entry);
+        }
+
+        alm.releaseAllImmediately();
+
+        assertEquals(0, alm.getAllEntries().count());
+    }
+
+    @Test
+    public void testCanRemoveImmediately_notShownLongEnough() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
+
+        // The entry has just been added so we should not remove immediately.
+        assertFalse(alm.canRemoveImmediately(entry.getKey()));
+    }
 
     @Test
     public void testHunRemovedLogging() {
@@ -233,7 +375,7 @@
                 BaseHeadsUpManager.HeadsUpEntry.class);
         headsUpEntry.mEntry = notifEntry;
 
-        hum.onAlertEntryRemoved(headsUpEntry);
+        hum.onEntryRemoved(headsUpEntry);
 
         verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
     }
@@ -286,7 +428,7 @@
         hum.showNotification(entry);
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -300,7 +442,7 @@
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
                 + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
 
-        assertFalse(hum.isAlerting(entry.getKey()));
+        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -314,7 +456,7 @@
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
                 + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -327,7 +469,7 @@
         hum.showNotification(entry);
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -341,7 +483,7 @@
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
                 + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -355,7 +497,7 @@
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
                 + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -370,11 +512,11 @@
         final boolean removedImmediately = hum.removeNotification(
                 entry.getKey(), /* releaseImmediately = */ false);
         assertFalse(removedImmediately);
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
 
         mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
 
-        assertFalse(hum.isAlerting(entry.getKey()));
+        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -387,12 +529,12 @@
         hum.showNotification(entry);
         mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
 
         final boolean removedImmediately = hum.removeNotification(
                 entry.getKey(), /* releaseImmediately = */ false);
         assertTrue(removedImmediately);
-        assertFalse(hum.isAlerting(entry.getKey()));
+        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -406,7 +548,7 @@
         final boolean removedImmediately = hum.removeNotification(
                 entry.getKey(), /* releaseImmediately = */ true);
         assertTrue(removedImmediately);
-        assertFalse(hum.isAlerting(entry.getKey()));
+        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -560,7 +702,7 @@
         // the notification and then updates it; in order to not log twice, the entry needs
         // to have a functional ExpandableNotificationRow that can keep track of whether it's
         // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
-        hum.onAlertEntryAdded(entryToPin);
+        hum.onEntryAdded(entryToPin);
 
         assertEquals(1, mUiEventLoggerFake.numLogs());
         assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 5f9c096..ca0e526 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -25,20 +25,22 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
 import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
 import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -47,20 +49,20 @@
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardStatusBarViewModelTest : SysuiTestCase() {
-    private val testScope = TestScope()
-    private val sceneTestUtils = SceneTestUtils(this)
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private val keyguardRepository = FakeKeyguardRepository()
     private val keyguardInteractor =
         KeyguardInteractor(
             keyguardRepository,
             mock<CommandQueue>(),
             PowerInteractorFactory.create().powerInteractor,
-            sceneTestUtils.fakeSceneContainerFlags,
+            kosmos.fakeSceneContainerFlags,
             FakeKeyguardBouncerRepository(),
             ConfigurationInteractor(FakeConfigurationRepository()),
             FakeShadeRepository(),
         ) {
-            sceneTestUtils.sceneInteractor()
+            kosmos.sceneInteractor
         }
     private val keyguardStatusBarInteractor =
         KeyguardStatusBarInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
index 5b4f4d3..95c934e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
@@ -88,6 +88,30 @@
     }
 
     @Test
+    fun setReadyToHandleTransition_whileTransitionRunning_fromBgThread_propagatesCallbacks() =
+        testScope.runTest {
+            runBlockingInBg { rootProvider.onTransitionStarted() }
+
+            runBlockingInBg {
+                // This causes the transition started callback to be propagated immediately, without
+                // the need to switch thread (as we're already in the correct one). We don't need a
+                // sync barrier on the bg thread as in
+                // setReadyToHandleTransition_whileTransitionRunning_propagatesCallbacks here.
+                scopedProvider.setReadyToHandleTransition(true)
+            }
+
+            listener.assertStarted()
+
+            runBlockingInBg { rootProvider.onTransitionProgress(1f) }
+
+            listener.assertLastProgress(1f)
+
+            runBlockingInBg { rootProvider.onTransitionFinished() }
+
+            listener.assertNotStarted()
+        }
+
+    @Test
     fun setReadyToHandleTransition_beforeAnyCallback_doesNotCrash() {
         testScope.runTest { scopedProvider.setReadyToHandleTransition(true) }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index e6b9d9b..b6a033a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -40,13 +40,18 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.source.UserRecord
@@ -98,8 +103,8 @@
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private lateinit var spyContext: Context
     private lateinit var userRepository: FakeUserRepository
     private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies
@@ -121,16 +126,17 @@
             SUPERVISED_USER_CREATION_APP_PACKAGE,
         )
 
-        utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+        kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
         mSetFlagsRule.enableFlags(AConfigFlags.FLAG_SWITCH_USER_ON_BG)
         spyContext = spy(context)
-        keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.fakeFeatureFlags)
+        keyguardReply =
+            KeyguardInteractorFactory.create(featureFlags = kosmos.fakeFeatureFlagsClassic)
         keyguardRepository = keyguardReply.repository
         userRepository = FakeUserRepository()
         refreshUsersScheduler =
             RefreshUsersScheduler(
                 applicationScope = testScope.backgroundScope,
-                mainDispatcher = utils.testDispatcher,
+                mainDispatcher = kosmos.testDispatcher,
                 repository = userRepository,
             )
     }
@@ -363,7 +369,7 @@
     fun actions_deviceUnlocked_fullScreen() {
         createUserInteractor()
         testScope.runTest {
-            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
 
             userRepository.setUserInfos(userInfos)
@@ -447,7 +453,7 @@
     fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() {
         createUserInteractor()
         testScope.runTest {
-            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
@@ -640,7 +646,7 @@
 
             val refreshUsersCallCount = userRepository.refreshUsersCallCount
 
-            utils.telephonyRepository.setCallState(1)
+            kosmos.fakeTelephonyRepository.setCallState(1)
             runCurrent()
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
@@ -792,7 +798,7 @@
     fun userRecordsFullScreen() {
         createUserInteractor()
         testScope.runTest {
-            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 3, includeGuest = false)
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
             userRepository.setUserInfos(userInfos)
@@ -901,7 +907,7 @@
     fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() {
         createUserInteractor()
         testScope.runTest {
-            utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
 
             val expandable = mock<Expandable>()
             underTest.showUserSwitcher(expandable)
@@ -1116,19 +1122,19 @@
                 manager = manager,
                 headlessSystemUserMode = headlessSystemUserMode,
                 applicationScope = testScope.backgroundScope,
-                telephonyInteractor = utils.telephonyInteractor(),
+                telephonyInteractor = kosmos.telephonyInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
-                backgroundDispatcher = utils.testDispatcher,
-                mainDispatcher = utils.testDispatcher,
+                backgroundDispatcher = kosmos.testDispatcher,
+                mainDispatcher = kosmos.testDispatcher,
                 activityManager = activityManager,
                 refreshUsersScheduler = refreshUsersScheduler,
                 guestUserInteractor =
                     GuestUserInteractor(
                         applicationContext = spyContext,
                         applicationScope = testScope.backgroundScope,
-                        mainDispatcher = utils.testDispatcher,
-                        backgroundDispatcher = utils.testDispatcher,
+                        mainDispatcher = kosmos.testDispatcher,
+                        backgroundDispatcher = kosmos.testDispatcher,
                         manager = manager,
                         repository = userRepository,
                         deviceProvisionedController = deviceProvisionedController,
@@ -1139,7 +1145,7 @@
                         resetOrExitSessionReceiver = resetOrExitSessionReceiver,
                     ),
                 uiEventLogger = uiEventLogger,
-                featureFlags = utils.fakeFeatureFlags,
+                featureFlags = kosmos.fakeFeatureFlagsClassic,
                 userRestrictionChecker = mock(),
             )
     }
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 8920d4d..1ed045f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -117,11 +117,11 @@
 import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
 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.SceneTestUtils;
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -356,8 +356,8 @@
     @Mock
     private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
 
-    private final SceneTestUtils mUtils = new SceneTestUtils(this);
-    private final TestScope mTestScope = mUtils.getTestScope();
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    private final TestScope mTestScope = mKosmos.getTestScope();
     private ShadeInteractor mShadeInteractor;
     private ShellTaskOrganizer mShellTaskOrganizer;
     private TaskViewTransitions mTaskViewTransitions;
@@ -408,8 +408,8 @@
         FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
 
         PowerInteractor powerInteractor = new PowerInteractor(
-                mUtils.getPowerRepository(),
-                mUtils.falsingCollector(),
+                mKosmos.getPowerRepository(),
+                mKosmos.getFalsingCollector(),
                 mock(ScreenOffAnimationController.class),
                 mStatusBarStateController);
 
@@ -417,7 +417,7 @@
                 mTestScope.getBackgroundScope(),
                 new SceneContainerRepository(
                         mTestScope.getBackgroundScope(),
-                        mUtils.fakeSceneContainerConfig()),
+                        mKosmos.getFakeSceneContainerConfig()),
                 powerInteractor,
                 mock(SceneLogger.class));
 
@@ -449,8 +449,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
-                mUtils.getTestDispatcher(),
-                mUtils.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 shadeRepository,
@@ -475,8 +475,8 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
-                mUtils.getTestDispatcher(),
-                mUtils.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 mock(KeyguardSecurityModel.class),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index 42ec8fed..f192de2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -26,12 +26,24 @@
     private val _isConfirmationRequired = MutableStateFlow(false)
     override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
 
+    private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
+    override val opPackageName = _opPackageName.asStateFlow()
+
     override fun setPrompt(
         promptInfo: PromptInfo,
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
-    ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false)
+        opPackageName: String,
+    ) =
+        setPrompt(
+            promptInfo,
+            userId,
+            gatekeeperChallenge,
+            kind,
+            forceConfirmation = false,
+            opPackageName = opPackageName
+        )
 
     fun setPrompt(
         promptInfo: PromptInfo,
@@ -39,12 +51,14 @@
         gatekeeperChallenge: Long?,
         kind: PromptKind,
         forceConfirmation: Boolean = false,
+        opPackageName: String? = null,
     ) {
         _promptInfo.value = promptInfo
         _userId.value = userId
         _challenge.value = gatekeeperChallenge
         _kind.value = kind
         _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
+        _opPackageName.value = opPackageName
     }
 
     override fun unsetPrompt() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
index 1f5af5c..482d60ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
@@ -18,7 +18,10 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
 
-val Kosmos.fakeCommunalRepository by Fixture { FakeCommunalRepository() }
+val Kosmos.fakeCommunalRepository by Fixture {
+    FakeCommunalRepository(applicationScope = applicationCoroutineScope)
+}
 
 val Kosmos.communalRepository by Fixture<CommunalRepository> { fakeCommunalRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt
new file mode 100644
index 0000000..f7665fb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.communalTutorialRepository by
+    Kosmos.Fixture<CommunalTutorialRepository> { fakeCommunalTutorialRepository }
+val Kosmos.fakeCommunalTutorialRepository by Kosmos.Fixture { FakeCommunalTutorialRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index c85c27e..20fa545 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -2,7 +2,6 @@
 
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
-import com.android.systemui.dagger.qualifiers.Background
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -12,13 +11,12 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.test.TestScope
 
 /** Fake implementation of [CommunalRepository]. */
 @OptIn(ExperimentalCoroutinesApi::class)
 class FakeCommunalRepository(
-    @Background applicationScope: CoroutineScope = TestScope(),
-    override var isCommunalEnabled: Boolean = false,
+    applicationScope: CoroutineScope,
+    override var isCommunalEnabled: Boolean = true,
     override val desiredScene: MutableStateFlow<CommunalSceneKey> =
         MutableStateFlow(CommunalSceneKey.DEFAULT),
 ) : CommunalRepository {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 397dc1a..d0c2d4b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -35,4 +35,13 @@
             }
         }
     }
+
+    private var isHostActive = false
+    override fun updateAppWidgetHostActive(active: Boolean) {
+        isHostActive = active
+    }
+
+    fun isHostActive(): Boolean {
+        return isHostActive
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
index 8430cf6..1753ca0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
@@ -17,12 +17,12 @@
 
 package com.android.systemui.communal.domain.interactor
 
-import android.appwidget.AppWidgetHost
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
 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.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -44,7 +44,7 @@
         smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
         tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
         communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(),
-        appWidgetHost: AppWidgetHost = mock(),
+        appWidgetHost: CommunalAppWidgetHost = mock(),
         editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(),
     ): WithDependencies {
         val withDeps =
@@ -67,6 +67,7 @@
             appWidgetHost,
             editWidgetsActivityStarter,
             CommunalInteractor(
+                testScope.backgroundScope,
                 communalRepository,
                 widgetRepository,
                 communalPrefsRepository,
@@ -90,7 +91,7 @@
         val keyguardRepository: FakeKeyguardRepository,
         val keyguardInteractor: KeyguardInteractor,
         val tutorialInteractor: CommunalTutorialInteractor,
-        val appWidgetHost: AppWidgetHost,
+        val appWidgetHost: CommunalAppWidgetHost,
         val editWidgetsActivityStarter: EditWidgetsActivityStarter,
         val communalInteractor: CommunalInteractor,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 7cbbaab..65579a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -23,11 +23,13 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.smartspace.data.repository.smartspaceRepository
 import com.android.systemui.util.mockito.mock
 
 val Kosmos.communalInteractor by Fixture {
     CommunalInteractor(
+        applicationScope = applicationCoroutineScope,
         communalRepository = communalRepository,
         widgetRepository = communalWidgetRepository,
         mediaRepository = communalMediaRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
index e5cadab..3ff2a3e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
@@ -32,7 +32,7 @@
         communalTutorialRepository: FakeCommunalTutorialRepository =
             FakeCommunalTutorialRepository(),
         communalRepository: FakeCommunalRepository =
-            FakeCommunalRepository(isCommunalEnabled = true),
+            FakeCommunalRepository(applicationScope = testScope.backgroundScope),
         keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
         keyguardInteractor: KeyguardInteractor =
             KeyguardInteractorFactory.create(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 975db3b..59f56dd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -127,6 +127,9 @@
 
     override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
+    private val _isEncryptedOrLockdown = MutableStateFlow(true)
+    override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
+
     override fun setQuickSettingsVisible(isVisible: Boolean) {
         _isQuickSettingsVisible.value = isVisible
     }
@@ -247,6 +250,10 @@
     override fun setKeyguardAlpha(alpha: Float) {
         _keyguardAlpha.value = alpha
     }
+
+    fun setIsEncryptedOrLockdown(value: Boolean) {
+        _isEncryptedOrLockdown.value = value
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
index 37b1b08..d8f0cec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,27 +16,32 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.appwidget.AppWidgetHost
 import com.android.systemui.communal.data.repository.communalMediaRepository
 import com.android.systemui.communal.data.repository.communalPrefsRepository
 import com.android.systemui.communal.data.repository.communalRepository
 import com.android.systemui.communal.data.repository.communalWidgetRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.smartspace.data.repository.smartspaceRepository
 import org.mockito.Mockito.mock
 
 val Kosmos.communalInteractor by
     Kosmos.Fixture {
         CommunalInteractor(
+            applicationScope = testScope.backgroundScope,
             communalRepository = communalRepository,
             widgetRepository = communalWidgetRepository,
             mediaRepository = communalMediaRepository,
             communalPrefsRepository = communalPrefsRepository,
             smartspaceRepository = smartspaceRepository,
             keyguardInteractor = keyguardInteractor,
-            appWidgetHost = mock(AppWidgetHost::class.java),
-            editWidgetsActivityStarter = mock(EditWidgetsActivityStarter::class.java),
+            appWidgetHost = mock(CommunalAppWidgetHost::class.java),
+            editWidgetsActivityStarter = editWidgetsActivityStarter,
         )
     }
+
+val Kosmos.editWidgetsActivityStarter by
+    Kosmos.Fixture<EditWidgetsActivityStarter> { mock(EditWidgetsActivityStarter::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
new file mode 100644
index 0000000..24670b1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.kosmos
+
+import android.content.applicationContext
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.bouncerRepository
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.util.time.systemClock
+
+/** Helper for using [Kosmos] from Java. */
+@Deprecated("Please convert your test to Kotlin and use [Kosmos] directly.")
+class KosmosJavaAdapter(
+    testCase: SysuiTestCase,
+) {
+
+    private val kosmos = Kosmos()
+
+    val testDispatcher by lazy { kosmos.testDispatcher }
+    val testScope by lazy { kosmos.testScope }
+    val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
+    val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
+    val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+    val configurationInteractor by lazy { kosmos.configurationInteractor }
+    val bouncerRepository by lazy { kosmos.bouncerRepository }
+    val communalRepository by lazy { kosmos.fakeCommunalRepository }
+    val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    val powerRepository by lazy { kosmos.fakePowerRepository }
+    val clock by lazy { kosmos.systemClock }
+    val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
+    val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
+    val statusBarStateController by lazy { kosmos.statusBarStateController }
+    val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
+    val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
+    val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
+    val sceneInteractor by lazy { kosmos.sceneInteractor }
+    val falsingCollector by lazy { kosmos.falsingCollector }
+    val powerInteractor by lazy { kosmos.powerInteractor }
+
+    init {
+        kosmos.applicationContext = testCase.context
+        kosmos.testCase = testCase
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
deleted file mode 100644
index d314a25..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene
-
-import android.content.Context
-import android.content.applicationContext
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.domain.interactor.authenticationInteractor
-import com.android.systemui.bouncer.data.repository.bouncerRepository
-import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.classifier.domain.interactor.FalsingInteractor
-import com.android.systemui.classifier.domain.interactor.falsingInteractor
-import com.android.systemui.classifier.falsingCollector
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.configurationInteractor
-import com.android.systemui.communal.data.repository.fakeCommunalRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.jank.interactionJankMonitor
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.statusbar.statusBarStateController
-import com.android.systemui.power.data.repository.fakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.scene.data.repository.SceneContainerRepository
-import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.screenOffAnimationController
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
-import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.telephony.domain.interactor.telephonyInteractor
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.user.domain.interactor.selectedUserInteractor
-import com.android.systemui.util.time.systemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-/**
- * Utilities for creating scene container framework related repositories, interactors, and
- * view-models for tests.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-@Deprecated("Please use Kosmos instead.")
-class SceneTestUtils {
-
-    val kosmos = Kosmos()
-
-    constructor(
-        context: Context,
-    ) {
-        kosmos.applicationContext = context
-    }
-
-    constructor(testCase: SysuiTestCase) : this(context = testCase.context) {
-        kosmos.testCase = testCase
-    }
-
-    val testDispatcher by lazy { kosmos.testDispatcher }
-    val testScope by lazy { kosmos.testScope }
-    val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
-    val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
-    val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository }
-    val authenticationRepository by lazy { kosmos.fakeAuthenticationRepository }
-    val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
-    val configurationInteractor by lazy { kosmos.configurationInteractor }
-    val telephonyRepository by lazy { kosmos.fakeTelephonyRepository }
-    val bouncerRepository by lazy { kosmos.bouncerRepository }
-    val communalRepository by lazy { kosmos.fakeCommunalRepository }
-    val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
-    val powerRepository by lazy { kosmos.fakePowerRepository }
-    val simBouncerRepository by lazy { kosmos.fakeSimBouncerRepository }
-    val clock by lazy { kosmos.systemClock }
-    val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
-    val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
-    val statusBarStateController by lazy { kosmos.statusBarStateController }
-    val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
-    val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
-
-    fun fakeSceneContainerRepository(): SceneContainerRepository {
-        return kosmos.sceneContainerRepository
-    }
-
-    fun fakeSceneKeys(): List<SceneKey> {
-        return kosmos.sceneKeys
-    }
-
-    fun fakeSceneContainerConfig(): SceneContainerConfig {
-        return kosmos.sceneContainerConfig
-    }
-
-    fun sceneInteractor(): SceneInteractor {
-        return kosmos.sceneInteractor
-    }
-
-    fun deviceEntryInteractor(): DeviceEntryInteractor {
-        return kosmos.deviceEntryInteractor
-    }
-
-    fun authenticationInteractor(): AuthenticationInteractor {
-        return kosmos.authenticationInteractor
-    }
-
-    fun keyguardInteractor(): KeyguardInteractor {
-        return kosmos.keyguardInteractor
-    }
-
-    fun communalInteractor(): CommunalInteractor {
-        return kosmos.communalInteractor
-    }
-
-    fun bouncerInteractor(): BouncerInteractor {
-        return kosmos.bouncerInteractor
-    }
-
-    fun notificationsPlaceholderViewModel(): NotificationsPlaceholderViewModel {
-        return kosmos.notificationsPlaceholderViewModel
-    }
-
-    fun bouncerViewModel(): BouncerViewModel {
-        return kosmos.bouncerViewModel
-    }
-
-    fun telephonyInteractor(): TelephonyInteractor {
-        return kosmos.telephonyInteractor
-    }
-
-    fun falsingInteractor(): FalsingInteractor {
-        return kosmos.falsingInteractor
-    }
-
-    fun falsingCollector(): FalsingCollector {
-        return kosmos.falsingCollector
-    }
-
-    fun powerInteractor(): PowerInteractor {
-        return kosmos.powerInteractor
-    }
-
-    fun selectedUserInteractor(): SelectedUserInteractor {
-        return kosmos.selectedUserInteractor
-    }
-
-    fun bouncerActionButtonInteractor(): BouncerActionButtonInteractor {
-        return kosmos.bouncerActionButtonInteractor
-    }
-}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index 2bca272..24b22f8 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -17,6 +17,7 @@
 
 import android.os.Handler
 import android.os.Looper
+import androidx.annotation.GuardedBy
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import java.util.concurrent.CopyOnWriteArrayList
@@ -41,8 +42,12 @@
 
     private val listeners = CopyOnWriteArrayList<TransitionProgressListener>()
 
-    @Volatile private var isReadyToHandleTransition = false
-    @Volatile private var isTransitionRunning = false
+    private val lock = Object()
+
+    @GuardedBy("lock") private var isReadyToHandleTransition = false
+    // Accessed only from progress thread
+    private var isTransitionRunning = false
+    // Accessed only from progress thread
     private var lastTransitionProgress = PROGRESS_UNSET
 
     init {
@@ -72,23 +77,44 @@
      * the transition progress events.
      *
      * Call it with readyToHandleTransition = false when listeners can't process the events.
+     *
+     * Note that this could be called by any thread.
      */
     fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
-        val progressHandler = this.progressHandler
-        if (isTransitionRunning && progressHandler != null) {
-            progressHandler.post {
-                if (isReadyToHandleTransition) {
-                    listeners.forEach { it.onTransitionStarted() }
-                    if (lastTransitionProgress != PROGRESS_UNSET) {
-                        listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
-                    }
-                } else {
-                    isTransitionRunning = false
-                    listeners.forEach { it.onTransitionFinished() }
-                }
+        synchronized(lock) {
+            this.isReadyToHandleTransition = isReadyToHandleTransition
+            val progressHandlerLocal = this.progressHandler
+            if (progressHandlerLocal != null) {
+                ensureInHandler(progressHandlerLocal) { reportLastProgressIfNeeded() }
             }
         }
-        this.isReadyToHandleTransition = isReadyToHandleTransition
+    }
+
+    /** Runs directly if called from the handler thread. Posts otherwise. */
+    private fun ensureInHandler(handler: Handler, f: () -> Unit) {
+        if (handler.looper.isCurrentThread) {
+            f()
+        } else {
+            handler.post(f)
+        }
+    }
+
+    private fun reportLastProgressIfNeeded() {
+        assertInProgressThread()
+        synchronized(lock) {
+            if (!isTransitionRunning) {
+                return
+            }
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionStarted() }
+                if (lastTransitionProgress != PROGRESS_UNSET) {
+                    listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+                }
+            } else {
+                isTransitionRunning = false
+                listeners.forEach { it.onTransitionFinished() }
+            }
+        }
     }
 
     override fun addCallback(listener: TransitionProgressListener) {
@@ -106,34 +132,42 @@
 
     override fun onTransitionStarted() {
         assertInProgressThread()
-        isTransitionRunning = true
-        if (isReadyToHandleTransition) {
-            listeners.forEach { it.onTransitionStarted() }
+        synchronized(lock) {
+            isTransitionRunning = true
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionStarted() }
+            }
         }
     }
 
     override fun onTransitionProgress(progress: Float) {
         assertInProgressThread()
-        if (isReadyToHandleTransition) {
-            listeners.forEach { it.onTransitionProgress(progress) }
+        synchronized(lock) {
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionProgress(progress) }
+            }
+            lastTransitionProgress = progress
         }
-        lastTransitionProgress = progress
     }
 
     override fun onTransitionFinishing() {
         assertInProgressThread()
-        if (isReadyToHandleTransition) {
-            listeners.forEach { it.onTransitionFinishing() }
+        synchronized(lock) {
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionFinishing() }
+            }
         }
     }
 
     override fun onTransitionFinished() {
         assertInProgressThread()
-        if (isReadyToHandleTransition) {
-            listeners.forEach { it.onTransitionFinished() }
+        synchronized(lock) {
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionFinished() }
+            }
+            isTransitionRunning = false
+            lastTransitionProgress = PROGRESS_UNSET
         }
-        isTransitionRunning = false
-        lastTransitionProgress = PROGRESS_UNSET
     }
 
     private fun assertInProgressThread() {
@@ -151,7 +185,7 @@
         }
     }
 
-    companion object {
-        private const val PROGRESS_UNSET = -1f
+    private companion object {
+        const val PROGRESS_UNSET = -1f
     }
 }
diff --git a/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java
new file mode 100644
index 0000000..2ef381e
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tests marked with this annotation are excluded when running under a Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
+ * an {@link ExcludeUnderRavenwood} annotation.
+ *
+ * This annotation only takes effect when the containing class has a {@code
+ * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
+ * .AssumptionViolatedException} which test infrastructure treats as being ignored.
+ *
+ * This annotation has no effect on any other non-Ravenwood test environments.
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcludeUnderRavenwood {
+}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java
new file mode 100644
index 0000000..0b2e32f
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tests marked with this annotation are included when running under a Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
+ * an {@link ExcludeUnderRavenwood} annotation.
+ *
+ * This annotation only takes effect when the containing class has a {@code
+ * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
+ * .AssumptionViolatedException} which test infrastructure treats as being ignored.
+ *
+ * This annotation has no effect on any other non-Ravenwood test environments.
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IncludeUnderRavenwood {
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index d967874..53da8ba 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -18,7 +18,9 @@
 
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.ExcludeUnderRavenwood;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.IncludeUnderRavenwood;
 
 import org.junit.Assume;
 import org.junit.rules.TestRule;
@@ -109,21 +111,52 @@
     }
 
     /**
-     * Test if the given {@link Description} has been marked with an {@link IgnoreUnderRavenwood}
-     * annotation, either at the method or class level.
+     * Determine if the given {@link Description} should be included when running under the
+     * Ravenwood test environment.
+     *
+     * A more specific method-level annotation always takes precedence over any class-level
+     * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
+     * an {@link ExcludeUnderRavenwood} annotation.
      */
-    private static boolean hasIgnoreUnderRavenwoodAnnotation(Description description) {
-        if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
-            return true;
-        } else if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
-            return true;
-        } else {
+    private boolean shouldIncludeUnderRavenwood(Description description) {
+        // Stopgap for http://g/ravenwood/EPAD-N5ntxM
+        if (description.getMethodName().endsWith("$noRavenwood")) {
             return false;
         }
+
+        // First, consult any method-level annotations
+        if (description.getAnnotation(IncludeUnderRavenwood.class) != null) {
+            return true;
+        }
+        if (description.getAnnotation(ExcludeUnderRavenwood.class) != null) {
+            return false;
+        }
+        if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+            return false;
+        }
+
+        // Otherwise, consult any class-level annotations
+        if (description.getTestClass().getAnnotation(IncludeUnderRavenwood.class) != null) {
+            return true;
+        }
+        if (description.getTestClass().getAnnotation(ExcludeUnderRavenwood.class) != null) {
+            return false;
+        }
+        if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+            return false;
+        }
+
+        // When no annotations have been requested, assume test should be included
+        return true;
     }
 
     @Override
     public Statement apply(Statement base, Description description) {
+        // No special treatment when running outside Ravenwood; run tests as-is
+        if (!IS_UNDER_RAVENWOOD) {
+            return base;
+        }
+
         if (ENABLE_PROBE_IGNORED) {
             return applyProbeIgnored(base, description);
         } else {
@@ -138,14 +171,7 @@
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                if (hasIgnoreUnderRavenwoodAnnotation(description)) {
-                    Assume.assumeFalse(IS_UNDER_RAVENWOOD);
-                }
-
-                // Stopgap for http://g/ravenwood/EPAD-N5ntxM
-                if (description.getMethodName().endsWith("$noRavenwood")) {
-                    Assume.assumeFalse(IS_UNDER_RAVENWOOD);
-                }
+                Assume.assumeTrue(shouldIncludeUnderRavenwood(description));
 
                 RavenwoodRuleImpl.init(RavenwoodRule.this);
                 try {
@@ -170,19 +196,17 @@
                 try {
                     base.evaluate();
                 } catch (Throwable t) {
-                    if (hasIgnoreUnderRavenwoodAnnotation(description)) {
-                        // This failure is expected, so eat the exception and report the
-                        // assumption failure that test authors expect
-                        Assume.assumeFalse(IS_UNDER_RAVENWOOD);
-                    }
+                    // If the test isn't included, eat the exception and report the
+                    // assumption failure that test authors expect; otherwise throw
+                    Assume.assumeTrue(shouldIncludeUnderRavenwood(description));
                     throw t;
                 } finally {
                     RavenwoodRuleImpl.reset(RavenwoodRule.this);
                 }
 
-                if (hasIgnoreUnderRavenwoodAnnotation(description) && IS_UNDER_RAVENWOOD) {
-                    fail("Test was annotated with IgnoreUnderRavenwood, but it actually "
-                            + "passed under Ravenwood; consider removing the annotation");
+                if (!shouldIncludeUnderRavenwood(description)) {
+                    fail("Test wasn't included under Ravenwood, but it actually "
+                            + "passed under Ravenwood; consider updating annotations");
                 }
             }
         };
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 92e00ee..727721d 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -5,4 +5,11 @@
   namespace: "autofill"
   description: "Guards Autofill Framework against Autofill-Credman integration"
   bug: "296907283"
+}
+
+flag {
+    name: "autofill_credman_integration_phase2"
+    namespace: "autofill"
+    description: "Guards against Autofill-Credman integration phase 2"
+    bug: "320730001"
 }
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 7fc1738..1234703 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -108,9 +108,9 @@
         mLastFlag = flag;
         if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
             Slog.v(TAG, "About to call CredAutofill service as secondary provider");
-            addSessionIdAndRequestIdToClientState(pendingFillRequest,
+            FillRequest request = addSessionIdAndRequestIdToClientState(pendingFillRequest,
                     pendingInlineSuggestionsRequest, id);
-            mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client);
+            mRemoteFillService.onFillCredentialRequest(request, client);
         } else {
             mRemoteFillService.onFillRequest(pendingFillRequest);
         }
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 2fbc3cd..0559708 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -140,8 +140,8 @@
     // Widget-related data handled as part of this restore operation
     private byte[] mWidgetData;
 
-    // Number of apps restored in this pass
-    private int mCount;
+    // Number of apps attempted to restore in this pass
+    private int mRestoreAttemptedAppsCount;
 
     // When did we start?
     private long mStartRealtime;
@@ -574,7 +574,8 @@
                     Slog.v(TAG, "No more packages; finishing restore");
                 }
                 int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
-                EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, mCount, millis);
+                EventLog.writeEvent(
+                        EventLogTags.RESTORE_SUCCESS, mRestoreAttemptedAppsCount, millis);
                 nextState = UnifiedRestoreState.FINAL;
                 return;
             }
@@ -582,7 +583,8 @@
             if (DEBUG) {
                 Slog.i(TAG, "Next restore package: " + mRestoreDescription);
             }
-            sendOnRestorePackage(pkgName);
+            mRestoreAttemptedAppsCount++;
+            sendOnRestorePackage(mRestoreAttemptedAppsCount, pkgName);
 
             Metadata metaInfo = mPmAgent.getRestoredMetadata(pkgName);
             if (metaInfo == null) {
@@ -810,7 +812,6 @@
         // And then finally start the restore on this agent
         try {
             initiateOneRestore(mCurrentPackage, metaInfo.versionCode);
-            ++mCount;
         } catch (Exception e) {
             Slog.e(TAG, "Error when attempting restore: " + e.toString());
             Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
@@ -1331,13 +1332,7 @@
         }
 
         // Tell the observer we're done
-        if (mObserver != null) {
-            try {
-                mObserver.restoreFinished(mStatus);
-            } catch (RemoteException e) {
-                Slog.d(TAG, "Restore observer died at restoreFinished");
-            }
-        }
+        sendEndRestore();
 
         // Clear any ongoing session timeout.
         backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
@@ -1651,10 +1646,10 @@
         }
     }
 
-    void sendOnRestorePackage(String name) {
+    void sendOnRestorePackage(int index, String name) {
         if (mObserver != null) {
             try {
-                mObserver.onUpdate(mCount, name);
+                mObserver.onUpdate(index, name);
             } catch (RemoteException e) {
                 Slog.d(TAG, "Restore observer died in onUpdate");
                 mObserver = null;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 53c0184c..e5a8c4f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -101,7 +101,7 @@
                     String deviceProfile = getNextArg();
                     final MacAddress macAddress = MacAddress.fromString(address);
                     mService.createNewAssociation(userId, packageName, macAddress,
-                            null, deviceProfile, false);
+                            /* displayName= */ deviceProfile, deviceProfile, false);
                 }
                 break;
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index ef61498..b6e1140 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -56,6 +56,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.ExceptionUtils;
 import android.util.Slog;
@@ -112,7 +113,7 @@
             Context.DEVICE_ID_DEFAULT + 1);
 
     @GuardedBy("mVirtualDeviceManagerLock")
-    private List<AssociationInfo> mActiveAssociations = new ArrayList<>();
+    private ArrayMap<String, AssociationInfo> mActiveAssociations = new ArrayMap<>();
 
     private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener =
             new CompanionDeviceManager.OnAssociationsChangedListener() {
@@ -343,34 +344,29 @@
 
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     void onCdmAssociationsChanged(List<AssociationInfo> associations) {
-        List<AssociationInfo> vdmAssociations = new ArrayList<>();
-        Set<Integer> activeAssociationIds = new HashSet<>();
+        ArrayMap<String, AssociationInfo> vdmAssociations = new ArrayMap<>();
         for (int i = 0; i < associations.size(); ++i) {
             AssociationInfo association = associations.get(i);
-            if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(association.getDeviceProfile())) {
-                vdmAssociations.add(association);
-                activeAssociationIds.add(association.getId());
+            if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(association.getDeviceProfile())
+                    && !association.isRevoked()) {
+                String persistentId =
+                        VirtualDeviceImpl.createPersistentDeviceId(association.getId());
+                vdmAssociations.put(persistentId, association);
             }
         }
         Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>();
-        Set<String> removedPersistentDeviceIds = new HashSet<>();
+        Set<String> removedPersistentDeviceIds;
         synchronized (mVirtualDeviceManagerLock) {
-            for (int i = 0; i < mActiveAssociations.size(); ++i) {
-                AssociationInfo associationInfo = mActiveAssociations.get(i);
-                if (!activeAssociationIds.contains(associationInfo.getId())) {
-                    removedPersistentDeviceIds.add(
-                            VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId()));
-                }
-            }
+            removedPersistentDeviceIds = mActiveAssociations.keySet();
+            removedPersistentDeviceIds.removeAll(vdmAssociations.keySet());
+            mActiveAssociations = vdmAssociations;
 
             for (int i = 0; i < mVirtualDevices.size(); i++) {
                 VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
-                if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) {
+                if (removedPersistentDeviceIds.contains(virtualDevice.getPersistentDeviceId())) {
                     virtualDevicesToRemove.add(virtualDevice);
                 }
             }
-
-            mActiveAssociations = vdmAssociations;
         }
 
         for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) {
@@ -577,6 +573,16 @@
             return Context.DEVICE_ID_DEFAULT;
         }
 
+        @Override // Binder call
+        public @Nullable CharSequence getDisplayNameForPersistentDeviceId(
+                @NonNull String persistentDeviceId) {
+            final AssociationInfo associationInfo;
+            synchronized (mVirtualDeviceManagerLock) {
+                associationInfo = mActiveAssociations.get(persistentDeviceId);
+            }
+            return associationInfo == null ? null : associationInfo.getDisplayName();
+        }
+
         // Binder call
         @Override
         public boolean isValidVirtualDeviceId(int deviceId) {
@@ -885,15 +891,9 @@
 
         @Override
         public @NonNull Set<String> getAllPersistentDeviceIds() {
-            Set<String> persistentIds = new ArraySet<>();
             synchronized (mVirtualDeviceManagerLock) {
-                for (int i = 0; i < mActiveAssociations.size(); ++i) {
-                    AssociationInfo associationInfo = mActiveAssociations.get(i);
-                    persistentIds.add(
-                            VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId()));
-                }
+                return Set.copyOf(mActiveAssociations.keySet());
             }
-            return persistentIds;
         }
 
         @Override
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a6ed498e..fdcd27d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -30,6 +30,14 @@
     ],
 }
 
+java_library_static {
+    name: "services-config-update",
+    srcs: [
+        "java/**/ConfigUpdateInstallReceiver.java",
+        "java/**/*.logtags",
+    ],
+}
+
 genrule {
     name: "services.core.protologsrc",
     srcs: [
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 5a44ac8..9d9e7c9 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1387,6 +1387,8 @@
                 case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
                 case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
                 case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
+                case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
+                case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
                     mContext.enforceCallingPermission(
                             android.Manifest.permission.BATTERY_STATS, null);
                     break;
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 329aac6..9f279b1 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -48,8 +48,6 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.am.DropboxRateLimiter;
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -62,14 +60,11 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.attribute.PosixFilePermissions;
-import java.util.AbstractMap;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Map;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.stream.Collectors;
 
 /**
  * Performs a number of miscellaneous, non-system-critical actions
@@ -337,12 +332,12 @@
      *
      * @param ctx Context
      * @param tombstone path to the tombstone
-     * @param tombstoneProto the parsed proto tombstone
+     * @param proto whether the tombstone is stored as proto
      * @param processName the name of the process corresponding to the tombstone
      * @param tmpFileLock the lock for reading/writing tmp files
      */
     public static void addTombstoneToDropBox(
-                Context ctx, File tombstone, Tombstone tombstoneProto, String processName,
+                Context ctx, File tombstone, boolean proto, String processName,
                 ReentrantLock tmpFileLock) {
         final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
         if (db == null) {
@@ -352,33 +347,31 @@
 
         // Check if we should rate limit and abort early if needed.
         DropboxRateLimiter.RateLimitResult rateLimitResult =
-                sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+                sDropboxRateLimiter.shouldRateLimit(
+                        proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName);
         if (rateLimitResult.shouldRateLimit()) return;
 
         HashMap<String, Long> timestamps = readTimestamps();
         try {
-            // Remove the memory data from the proto.
-            Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto);
-
-            final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray();
-
-            // Use JNI to call the c++ proto to text converter and add the headers to the tombstone.
-            String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate())
-                    .append(rateLimitResult.createHeader())
-                    .append(getTombstoneText(tombstoneBytes))
-                    .toString();
-
-            // Add the tombstone without memory data to dropbox.
-            db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory);
-
-            // Add the tombstone proto to dropbox.
-            if (recordFileTimestamp(tombstone, timestamps)) {
-                tmpFileLock.lock();
-                try {
-                    addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult);
-                } finally {
-                    tmpFileLock.unlock();
+            if (proto) {
+                if (recordFileTimestamp(tombstone, timestamps)) {
+                    // We need to attach the count indicating the number of dropped dropbox entries
+                    // due to rate limiting. Do this by enclosing the proto tombsstone in a
+                    // container proto that has the dropped entry count and the proto tombstone as
+                    // bytes (to avoid the complexity of reading and writing nested protos).
+                    tmpFileLock.lock();
+                    try {
+                        addAugmentedProtoToDropbox(tombstone, db, rateLimitResult);
+                    } finally {
+                        tmpFileLock.unlock();
+                    }
                 }
+            } else {
+                // Add the header indicating how many events have been dropped due to rate limiting.
+                final String headers = getBootHeadersToLogAndUpdate()
+                        + rateLimitResult.createHeader();
+                addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
+                                 TAG_TOMBSTONE);
             }
         } catch (IOException e) {
             Slog.e(TAG, "Can't log tombstone", e);
@@ -387,8 +380,11 @@
     }
 
     private static void addAugmentedProtoToDropbox(
-                File tombstone, byte[] tombstoneBytes, DropBoxManager db,
+                File tombstone, DropBoxManager db,
                 DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
+        // Read the proto tombstone file as bytes.
+        final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
+
         final File tombstoneProtoWithHeaders = File.createTempFile(
                 tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
         Files.setPosixFilePermissions(
@@ -421,8 +417,6 @@
         }
     }
 
-    private static native String getTombstoneText(byte[] tombstoneBytes);
-
     private static void addLastkToDropBox(
             DropBoxManager db, HashMap<String, Long> timestamps,
             String headers, String footers, String filename, int maxSize,
@@ -440,31 +434,6 @@
         addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag);
     }
 
-    /** Removes memory information from the Tombstone proto. */
-    @VisibleForTesting
-    public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) {
-        Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder()
-                .clearMemoryMappings()
-                .clearThreads()
-                .putAllThreads(tombstoneProto.getThreadsMap().entrySet()
-                        .stream()
-                        .map(BootReceiver::clearMemoryDump)
-                        .collect(Collectors.toMap(e->e.getKey(), e->e.getValue())));
-
-        if (tombstoneProto.hasSignalInfo()) {
-            tombstoneBuilder.setSignalInfo(
-                    tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata());
-        }
-
-        return tombstoneBuilder.build();
-    }
-
-    private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump(
-            Map.Entry<Integer, TombstoneProtos.Thread> e) {
-        return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>(
-            e.getKey(), e.getValue().toBuilder().clearMemoryDump().build());
-    }
-
     private static void addFileToDropBox(
             DropBoxManager db, HashMap<String, Long> timestamps,
             String headers, String filename, int maxSize, String tag) throws IOException {
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 70bd4b3..c391642 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -34,11 +34,11 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.SensitiveContentPackages.PackageInfo;
 import com.android.server.wm.WindowManagerInternal;
 
-import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -54,6 +54,10 @@
     private @Nullable MediaProjectionManager mProjectionManager;
     private @Nullable WindowManagerInternal mWindowManager;
 
+    final Object mSensitiveContentProtectionLock = new Object();
+    @GuardedBy("mSensitiveContentProtectionLock")
+    private boolean mProjectionActive = false;
+
     private final MediaProjectionManager.Callback mProjectionCallback =
             new MediaProjectionManager.Callback() {
                 @Override
@@ -132,14 +136,23 @@
     }
 
     private void onProjectionStart() {
-        StatusBarNotification[] notifications;
-        try {
-            notifications = mNotificationListener.getActiveNotifications();
-        } catch (SecurityException e) {
-            Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
-            notifications = new StatusBarNotification[0];
+        synchronized (mSensitiveContentProtectionLock) {
+            mProjectionActive = true;
+            updateAppsThatShouldBlockScreenCapture();
         }
+    }
 
+    private void onProjectionEnd() {
+        synchronized (mSensitiveContentProtectionLock) {
+            mProjectionActive = false;
+
+            // notify windowmanager to clear any sensitive notifications observed during projection
+            // session
+            mWindowManager.clearBlockedApps();
+        }
+    }
+
+    private void updateAppsThatShouldBlockScreenCapture() {
         RankingMap rankingMap;
         try {
             rankingMap = mNotificationListener.getCurrentRanking();
@@ -148,41 +161,98 @@
             rankingMap = null;
         }
 
-        // notify windowmanager of any currently posted sensitive content notifications
-        Set<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(
-                notifications,
-                rankingMap);
-
-        mWindowManager.setShouldBlockScreenCaptureForApp(packageInfos);
+        updateAppsThatShouldBlockScreenCapture(rankingMap);
     }
 
-    private void onProjectionEnd() {
-        // notify windowmanager to clear any sensitive notifications observed during projection
-        // session
-        mWindowManager.setShouldBlockScreenCaptureForApp(Collections.emptySet());
-    }
-
-    private Set<PackageInfo> getSensitivePackagesFromNotifications(
-            StatusBarNotification[] notifications, RankingMap rankingMap) {
-        if (rankingMap == null) {
-            Log.w(TAG, "Ranking map not initialized.");
-            return Collections.emptySet();
+    private void updateAppsThatShouldBlockScreenCapture(RankingMap rankingMap) {
+        StatusBarNotification[] notifications;
+        try {
+            notifications = mNotificationListener.getActiveNotifications();
+        } catch (SecurityException e) {
+            Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
+            notifications = new StatusBarNotification[0];
         }
 
-        Set<PackageInfo> sensitivePackages = new ArraySet<>();
+        // notify windowmanager of any currently posted sensitive content notifications
+        ArraySet<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(
+                notifications, rankingMap);
+
+        mWindowManager.addBlockScreenCaptureForApps(packageInfos);
+    }
+
+    private ArraySet<PackageInfo> getSensitivePackagesFromNotifications(
+            @NonNull StatusBarNotification[] notifications, RankingMap rankingMap) {
+        ArraySet<PackageInfo> sensitivePackages = new ArraySet<>();
+        if (rankingMap == null) {
+            Log.w(TAG, "Ranking map not initialized.");
+            return sensitivePackages;
+        }
+
         for (StatusBarNotification sbn : notifications) {
-            NotificationListenerService.Ranking ranking =
-                    rankingMap.getRawRankingObject(sbn.getKey());
-            if (ranking != null && ranking.hasSensitiveContent()) {
-                PackageInfo info = new PackageInfo(sbn.getPackageName(), sbn.getUid());
+            PackageInfo info = getSensitivePackageFromNotification(sbn, rankingMap);
+            if (info != null) {
                 sensitivePackages.add(info);
             }
         }
         return sensitivePackages;
     }
 
-    // TODO(b/317251408): add trigger that updates on onNotificationPosted,
-    //  onNotificationRankingUpdate and onListenerConnected
+    private PackageInfo getSensitivePackageFromNotification(StatusBarNotification sbn,
+            RankingMap rankingMap) {
+        if (sbn == null) {
+            Log.w(TAG, "Unable to protect null notification");
+            return null;
+        }
+        if (rankingMap == null) {
+            Log.w(TAG, "Ranking map not initialized.");
+            return null;
+        }
+
+        NotificationListenerService.Ranking ranking = rankingMap.getRawRankingObject(sbn.getKey());
+        if (ranking != null && ranking.hasSensitiveContent()) {
+            return new PackageInfo(sbn.getPackageName(), sbn.getUid());
+        }
+        return null;
+    }
+
     @VisibleForTesting
-    static class NotificationListener extends NotificationListenerService {}
+    class NotificationListener extends NotificationListenerService {
+        @Override
+        public void onListenerConnected() {
+            super.onListenerConnected();
+            // Projection started before notification listener was connected
+            synchronized (mSensitiveContentProtectionLock) {
+                if (mProjectionActive) {
+                    updateAppsThatShouldBlockScreenCapture();
+                }
+            }
+        }
+
+        @Override
+        public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+            super.onNotificationPosted(sbn, rankingMap);
+            synchronized (mSensitiveContentProtectionLock) {
+                if (!mProjectionActive) {
+                    return;
+                }
+
+                // notify windowmanager of any currently posted sensitive content notifications
+                PackageInfo packageInfo = getSensitivePackageFromNotification(sbn, rankingMap);
+
+                if (packageInfo != null) {
+                    mWindowManager.addBlockScreenCaptureForApps(new ArraySet(Set.of(packageInfo)));
+                }
+            }
+        }
+
+        @Override
+        public void onNotificationRankingUpdate(RankingMap rankingMap) {
+            super.onNotificationRankingUpdate(rankingMap);
+            synchronized (mSensitiveContentProtectionLock) {
+                if (mProjectionActive) {
+                    updateAppsThatShouldBlockScreenCapture(rankingMap);
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 2b35231..ea1b0f5 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1076,6 +1076,7 @@
             final UserManager userManager = mContext.getSystemService(UserManager.class);
             final List<UserInfo> users = userManager.getUsers();
 
+            extendWatchdogTimeout("#onReset might be slow");
             mStorageSessionController.onReset(mVold, () -> {
                 mHandler.removeCallbacksAndMessages(null);
             });
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index f921b0b..bd67cf420 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1126,6 +1126,21 @@
             return;
         }
 
+        int phoneId = -1;
+        int subscriptionId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+        if(Flags.preventSystemServerAndPhoneDeadlock()) {
+            // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+            // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+            if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                if (DBG) {
+                    log("invalid subscription id, use default id");
+                }
+            } else { //APP specify subID
+                subscriptionId = subId;
+            }
+            phoneId = getPhoneIdFromSubId(subscriptionId);
+        }
+
         synchronized (mRecords) {
             // register
             IBinder b = callback.asBinder();
@@ -1145,17 +1160,23 @@
             r.renounceFineLocationAccess = renounceFineLocationAccess;
             r.callerUid = Binder.getCallingUid();
             r.callerPid = Binder.getCallingPid();
-            // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
-            // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
-            if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-                if (DBG) {
-                    log("invalid subscription id, use default id");
+
+            if(!Flags.preventSystemServerAndPhoneDeadlock()) {
+                // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+                // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+                if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                    if (DBG) {
+                        log("invalid subscription id, use default id");
+                    }
+                    r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+                } else {//APP specify subID
+                    r.subId = subId;
                 }
-                r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-            } else {//APP specify subID
-                r.subId = subId;
+                r.phoneId = getPhoneIdFromSubId(r.subId);
+            } else {
+                r.subId = subscriptionId;
+                r.phoneId = phoneId;
             }
-            r.phoneId = getPhoneIdFromSubId(r.subId);
             r.eventList = events;
 
             if (DBG) {
@@ -1893,8 +1914,14 @@
     }
 
     private void notifyCarrierNetworkChangeWithPermission(int subId, boolean active) {
+        int phoneId = -1;
+        if(Flags.preventSystemServerAndPhoneDeadlock()) {
+            phoneId = getPhoneIdFromSubId(subId);
+        }
         synchronized (mRecords) {
-            int phoneId = getPhoneIdFromSubId(subId);
+            if(!Flags.preventSystemServerAndPhoneDeadlock()) {
+                phoneId = getPhoneIdFromSubId(subId);
+            }
             mCarrierNetworkChangeState[phoneId] = active;
 
             if (VDBG) {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 66a10e4a..cd8be33 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,8 +16,10 @@
 
 package com.android.server;
 
+import static android.app.Flags.modesApi;
 import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
 import static android.app.UiModeManager.DEFAULT_PRIORITY;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
@@ -50,6 +52,7 @@
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
 import android.app.UiModeManager;
+import android.app.UiModeManager.AttentionModeThemeOverlayType;
 import android.app.UiModeManager.NightModeCustomReturnType;
 import android.app.UiModeManager.NightModeCustomType;
 import android.content.BroadcastReceiver;
@@ -134,6 +137,7 @@
     private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     private int mNightMode = UiModeManager.MODE_NIGHT_NO;
     private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
+    private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
     private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
     private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0);
     private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
@@ -839,6 +843,8 @@
                                 ? customModeType
                                 : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
                         mNightMode = mode;
+                        //deactivates AttentionMode if user toggles DarkTheme
+                        mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
                         resetNightModeOverrideLocked();
                         persistNightMode(user);
                         // on screen off will update configuration instead
@@ -879,6 +885,29 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+        @Override
+            public void setAttentionModeThemeOverlay(
+                @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
+            setAttentionModeThemeOverlay_enforcePermission();
+
+            synchronized (mLock) {
+                if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) {
+                    mAttentionModeThemeOverlay = attentionModeThemeOverlayType;
+                    Binder.withCleanCallingIdentity(()-> updateLocked(0, 0));
+                }
+            }
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+        @Override
+        public @AttentionModeThemeOverlayType int getAttentionModeThemeOverlay() {
+            getAttentionModeThemeOverlay_enforcePermission();
+            synchronized (mLock) {
+                return mAttentionModeThemeOverlay;
+            }
+        }
+
         @Override
         public void setApplicationNightMode(@UiModeManager.NightMode int mode) {
             switch (mode) {
@@ -1406,7 +1435,7 @@
             pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
             pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
             pw.print("/"); pw.print(mOverrideNightModeOff);
-
+            pw.print("  mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
             pw.print(" mNightModeLocked="); pw.println(mNightModeLocked);
 
             pw.print("  mCarModeEnabled="); pw.print(mCarModeEnabled);
@@ -1685,7 +1714,7 @@
     }
 
     @UiModeManager.NightMode
-    private int getComputedUiModeConfiguration(@UiModeManager.NightMode int uiMode) {
+    private int getComputedUiModeConfiguration(int uiMode) {
         uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
                 : Configuration.UI_MODE_NIGHT_NO;
         uiMode &= mComputedNightMode ? ~Configuration.UI_MODE_NIGHT_NO
@@ -1980,18 +2009,26 @@
     }
 
     private void updateComputedNightModeLocked(boolean activate) {
-        mComputedNightMode = activate;
-        if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
-            return;
+        boolean newComputedValue = activate;
+        if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+            if (mOverrideNightModeOn && !newComputedValue) {
+                newComputedValue = true;
+            } else if (mOverrideNightModeOff && newComputedValue) {
+                newComputedValue = false;
+            }
         }
-        if (mOverrideNightModeOn && !mComputedNightMode) {
-            mComputedNightMode = true;
-            return;
+
+        if (modesApi()) {
+            // Computes final night mode values based on Attention Mode.
+            mComputedNightMode = switch (mAttentionModeThemeOverlay) {
+                case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true;
+                case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false;
+                default -> newComputedValue; // case OFF
+            };
+        } else {
+            mComputedNightMode = newComputedValue;
         }
-        if (mOverrideNightModeOff && mComputedNightMode) {
-            mComputedNightMode = false;
-            return;
-        }
+
         if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
                 && mTwilightManager.getLastTwilightState() != null)) {
             resetNightModeOverrideLocked();
diff --git a/services/core/java/com/android/server/ZramWriteback.java b/services/core/java/com/android/server/ZramWriteback.java
index 5d97def..39ca29e 100644
--- a/services/core/java/com/android/server/ZramWriteback.java
+++ b/services/core/java/com/android/server/ZramWriteback.java
@@ -177,7 +177,6 @@
         // back at later point if they remain untouched.
         js.schedule(new JobInfo.Builder(MARK_IDLE_JOB_ID, sZramWriteback)
                         .setMinimumLatency(TimeUnit.MINUTES.toMillis(markIdleDelay))
-                        .setOverrideDeadline(TimeUnit.MINUTES.toMillis(markIdleDelay))
                         .build());
 
         // Schedule a one time job to flush idle pages to disk.
diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/adaptiveauth/OWNERS
new file mode 100644
index 0000000..b188105
--- /dev/null
+++ b/services/core/java/com/android/server/adaptiveauth/OWNERS
@@ -0,0 +1 @@
[email protected]
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 979449f..9b1fade 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5400,13 +5400,13 @@
                 return msg;
             }
             mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
-                    hostingRecord, true);
+                    true);
             if (isolated) {
                 r.isolationHostProc = app;
             }
         } else {
             mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
-                    hostingRecord, false);
+                    false);
         }
 
         if (r.fgRequired) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f6d954a..c9bd0b4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7823,12 +7823,7 @@
     @Override
     public void registerUidObserver(IUidObserver observer, int which, int cutpoint,
             String callingPackage) {
-        if (!hasUsageStatsPermission(callingPackage)) {
-            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
-                    "registerUidObserver");
-        }
-        mUidObserverController.register(observer, which, cutpoint, callingPackage,
-                Binder.getCallingUid(), /*uids*/null);
+        registerUidObserverForUids(observer, which, cutpoint, callingPackage, null /* uids */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index b90e5cd..c857235 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -81,18 +81,18 @@
     private static final int FOREACH_ACTION_REMOVE_ITEM = 1;
     private static final int FOREACH_ACTION_STOP_ITERATION = 2;
 
-    private static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
+    @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
 
     @VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore";
 
     @VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo";
 
-    private final Object mLock = new Object();
+    @VisibleForTesting final Object mLock = new Object();
 
-    private boolean mEnabled = false;
+    @VisibleForTesting boolean mEnabled = false;
 
     /** Initialized in {@link #init} and read-only after that. */
-    private ActivityManagerService mService;
+    @VisibleForTesting ActivityManagerService mService;
 
     /** Initialized in {@link #init} and read-only after that. */
     private Handler mHandler;
@@ -112,7 +112,7 @@
      *
      * <p>Initialized in {@link #init} and read-only after that. No lock is needed.
      */
-    private int mAppStartInfoHistoryListSize;
+    @VisibleForTesting int mAppStartInfoHistoryListSize;
 
     @GuardedBy("mLock")
     private final ProcessMap<AppStartInfoContainer> mData;
@@ -146,7 +146,8 @@
      * Key is timestamp of launch from {@link #ActivityMetricsLaunchObserver}.
      */
     @GuardedBy("mLock")
-    private ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
+    @VisibleForTesting
+    final ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
 
     AppStartInfoTracker() {
         mCallbacks = new SparseArray<>();
@@ -229,7 +230,7 @@
                 ApplicationStartInfo info = mInProgRecords.get(id);
                 info.setStartType((int) temperature);
                 addBaseFieldsFromProcessRecord(info, app);
-                addStartInfoLocked(info);
+                mInProgRecords.put(id, addStartInfoLocked(info));
             } else {
                 mInProgRecords.remove(id);
             }
@@ -262,6 +263,7 @@
             ApplicationStartInfo info = mInProgRecords.get(id);
             info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
             info.setLaunchMode(launchMode);
+            checkCompletenessAndCallback(info);
         }
     }
 
@@ -281,7 +283,7 @@
     }
 
     public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
-                ServiceRecord serviceRecord, HostingRecord hostingRecord, boolean cold) {
+                ServiceRecord serviceRecord, boolean cold) {
         synchronized (mLock) {
             if (!mEnabled) {
                 return;
@@ -297,7 +299,9 @@
                     && serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
                     ? ApplicationStartInfo.START_REASON_JOB
                     : ApplicationStartInfo.START_REASON_SERVICE);
-            start.setIntent(serviceRecord.intent.getIntent());
+            if (serviceRecord.intent != null) {
+                start.setIntent(serviceRecord.intent.getIntent());
+            }
             addStartInfoLocked(start);
         }
     }
@@ -378,6 +382,7 @@
         start.setPackageUid(app.info.uid);
         start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
         start.setProcessName(app.processName);
+        start.setPackageName(app.info.packageName);
     }
 
     void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) {
@@ -419,12 +424,12 @@
     }
 
     private void addTimestampToStart(ProcessRecord app, long timeNs, int key) {
-        addTimestampToStart(app.processName, app.uid, timeNs, key);
+        addTimestampToStart(app.info.packageName, app.uid, timeNs, key);
     }
 
-    private void addTimestampToStart(String processName, int uid, long timeNs, int key) {
+    private void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
         synchronized (mLock) {
-            AppStartInfoContainer container = mData.get(processName, uid);
+            AppStartInfoContainer container = mData.get(packageName, uid);
             if (container == null) {
                 // Record was not created, discard new data.
                 return;
@@ -443,11 +448,11 @@
 
         final ApplicationStartInfo info = new ApplicationStartInfo(raw);
 
-        AppStartInfoContainer container = mData.get(raw.getProcessName(), raw.getRealUid());
+        AppStartInfoContainer container = mData.get(raw.getPackageName(), raw.getRealUid());
         if (container == null) {
             container = new AppStartInfoContainer(mAppStartInfoHistoryListSize);
             container.mUid = info.getRealUid();
-            mData.put(raw.getProcessName(), raw.getRealUid(), container);
+            mData.put(raw.getPackageName(), raw.getRealUid(), container);
         }
         container.addStartInfoLocked(info);
 
@@ -486,6 +491,9 @@
         if (!mEnabled) {
             return;
         }
+        if (maxNum == 0) {
+            maxNum = APP_START_INFO_HISTORY_LIST_SIZE;
+        }
         final long identity = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -891,6 +899,7 @@
                 mProcStartInfoFile.delete();
             }
             mData.getMap().clear();
+            mInProgRecords.clear();
         }
     }
 
@@ -960,6 +969,10 @@
 
     /** Convenience method to obtain timestamp of beginning of start.*/
     private static long getStartTimestamp(ApplicationStartInfo startInfo) {
+        if (startInfo.getStartupTimestamps() == null
+                    || !startInfo.getStartupTimestamps().containsKey(START_TIMESTAMP_LAUNCH)) {
+            return -1;
+        }
         return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH);
     }
 
@@ -997,7 +1010,6 @@
                 if (oldestIndex >= 0) {
                     mInfos.remove(oldestIndex);
                 }
-                mInfos.remove(0);
             }
             mInfos.add(info);
             Collections.sort(mInfos, (a, b) ->
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 8fa0089..2ed217a 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2882,6 +2882,10 @@
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
+        if (proxyAttributionTag != null
+                && !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) {
+            proxyAttributionTag = null;
+        }
 
         synchronized (this) {
             final Ops ops = getOpsLocked(uid, packageName, attributionTag,
@@ -3476,6 +3480,10 @@
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
+        if (proxyAttributionTag != null
+                && !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) {
+            proxyAttributionTag = null;
+        }
 
         boolean isRestricted = false;
         int startType = START_TYPE_FAILED;
@@ -4329,6 +4337,36 @@
         return false;
     }
 
+    /**
+     * Checks to see if the attribution tag is defined in either package or proxyPackage.
+     * This method is intended for ProxyAttributionTag validation and returns false
+     * if it does not exist in either one of them.
+     *
+     * @param packageName Name of the package
+     * @param proxyPackageName Name of the proxy package
+     * @param attributionTag attribution tag to be checked
+     *
+     * @return boolean specifying if attribution tag is valid or not
+     */
+    private boolean isAttributionTagDefined(@Nullable String packageName,
+                                          @Nullable String proxyPackageName,
+                                          @Nullable String attributionTag) {
+        if (packageName == null) {
+            return false;
+        } else if (attributionTag == null) {
+            return true;
+        }
+        PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+        if (proxyPackageName != null) {
+            AndroidPackage proxyPkg = pmInt.getPackage(proxyPackageName);
+            if (proxyPkg != null && isAttributionInPackage(proxyPkg, attributionTag)) {
+                return true;
+            }
+        }
+        AndroidPackage pkg = pmInt.getPackage(packageName);
+        return isAttributionInPackage(pkg, attributionTag);
+    }
+
     private void logVerifyAndGetBypassFailure(int uid, @NonNull SecurityException e,
             @NonNull String methodName) {
         if (Process.isIsolated(uid)) {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index d5d8fd2..8fd2ee2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -20,6 +20,7 @@
 // TODO(b/141025588): Create separate internal and external permissions for AuthService.
 // TODO(b/141025588): Get rid of the USE_FINGERPRINT permission.
 
+import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -304,6 +305,9 @@
             if (promptInfo.containsPrivateApiConfigurations()) {
                 checkInternalPermission();
             }
+            if (promptInfo.containsManageBioApiConfigurations()) {
+                checkManageBiometricPermission();
+            }
 
             final long identity = Binder.clearCallingIdentity();
             try {
@@ -984,6 +988,11 @@
                 "Must have USE_BIOMETRIC_INTERNAL permission");
     }
 
+    private void checkManageBiometricPermission() {
+        getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG,
+                "Must have MANAGE_BIOMETRIC_DIALOG permission");
+    }
+
     private void checkPermission() {
         if (getContext().checkCallingOrSelfPermission(USE_FINGERPRINT)
                 != PackageManager.PERMISSION_GRANTED) {
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index 1f59b57..c260f10 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -17,6 +17,7 @@
 package com.android.server.grammaticalinflection;
 
 import android.annotation.Nullable;
+import android.content.res.Configuration;
 
 /**
  * System-server internal interface to the {@link android.app.GrammaticalInflectionManager}.
@@ -37,5 +38,14 @@
      * at the time this is called, to be referenced later when the app is installed.
      */
     public abstract void stageAndApplyRestoredPayload(byte[] payload, int userId);
+
+    /**
+     * Get the current system grammatical gender of privileged application.
+     *
+     * @return the value of grammatical gender
+     *
+     * @see Configuration#getGrammaticalGender
+     */
+    public abstract @Configuration.GrammaticalGender int getSystemGrammaticalGender(int userId);
 }
 
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 68848a2..6eb7e95 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -19,9 +19,12 @@
 import static android.app.Flags.systemTermsOfAddressEnabled;
 import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
 
+import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
+
 import android.annotation.Nullable;
 import android.app.GrammaticalInflectionManager;
 import android.app.IGrammaticalInflectionManager;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
@@ -30,6 +33,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.SystemProperties;
+import android.permission.PermissionManager;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -75,6 +79,8 @@
 
     private PackageManagerInternal mPackageManagerInternal;
     private GrammaticalInflectionService.GrammaticalInflectionBinderService mBinderService;
+    private PermissionManager mPermissionManager;
+    private Context mContext;
 
     /**
      * Initializes the system service.
@@ -88,11 +94,12 @@
      */
     public GrammaticalInflectionService(Context context) {
         super(context);
+        mContext = context;
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mBackupHelper = new GrammaticalInflectionBackupHelper(
-                this, context.getPackageManager());
+        mBackupHelper = new GrammaticalInflectionBackupHelper(this, context.getPackageManager());
         mBinderService = new GrammaticalInflectionBinderService();
+        mPermissionManager = context.getSystemService(PermissionManager.class);
     }
 
     @Override
@@ -112,7 +119,7 @@
         }
 
         @Override
-        public void setSystemWideGrammaticalGender(int userId, int grammaticalGender) {
+        public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
             checkCallerIsSystem();
             checkSystemTermsOfAddressIsEnabled();
             GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
@@ -120,16 +127,17 @@
         }
 
         @Override
-        public int getSystemGrammaticalGender(int userId) {
+        public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
             checkSystemTermsOfAddressIsEnabled();
-            return GrammaticalInflectionService.this.getSystemGrammaticalGender(userId);
+            return GrammaticalInflectionService.this.getSystemGrammaticalGender(attributionSource,
+                    userId);
         }
 
         @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out,
                 FileDescriptor err, String[] args, ShellCallback callback,
                 ResultReceiver resultReceiver) {
-            (new GrammaticalInflectionShellCommand(mBinderService))
+            (new GrammaticalInflectionShellCommand(mBinderService, mContext.getAttributionSource()))
                     .exec(this, in, out, err, args, callback, resultReceiver);
         }
     };
@@ -148,6 +156,13 @@
         public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
             mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
         }
+
+        @Override
+        public int getSystemGrammaticalGender(int userId) {
+            checkCallerIsSystem();
+            return GrammaticalInflectionService.this.getSystemGrammaticalGender(
+                    mContext.getAttributionSource(), userId);
+        }
     }
 
     protected int getApplicationGrammaticalGender(String appPackageName, int userId) {
@@ -211,9 +226,24 @@
         }
     }
 
-    // TODO(b/298591009): Add a new AppOp value for the apps that want to access the grammatical
-    //  gender.
-    public int getSystemGrammaticalGender(int userId) {
+    public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
+        String packageName = attributionSource.getPackageName();
+        if (packageName == null) {
+            Log.d(TAG, "Package name is null.");
+            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        }
+
+        int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) != callingUid) {
+            Log.d(TAG,
+                    "Package " + packageName + " does not belong to the calling uid " + callingUid);
+            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        }
+
+        if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
+            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        }
+
         synchronized (mLock) {
             final File file = getGrammaticalGenderFile(userId);
             if (!file.exists()) {
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
index d223728..cdda692 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
@@ -21,6 +21,7 @@
 import android.app.ActivityManager;
 import android.app.GrammaticalInflectionManager;
 import android.app.IGrammaticalInflectionManager;
+import android.content.AttributionSource;
 import android.content.res.Configuration;
 import android.os.RemoteException;
 import android.os.ShellCommand;
@@ -44,9 +45,12 @@
     }
 
     private final IGrammaticalInflectionManager mBinderService;
+    private AttributionSource mAttributionSource;
 
-    GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager) {
+    GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager,
+            AttributionSource attributionSource) {
         mBinderService = grammaticalInflectionManager;
+        mAttributionSource = attributionSource;
     }
 
     @Override
@@ -115,7 +119,7 @@
         } while (true);
 
         try {
-            mBinderService.setSystemWideGrammaticalGender(userId, grammaticalGender);
+            mBinderService.setSystemWideGrammaticalGender(grammaticalGender, userId);
         } catch (RemoteException e) {
             getOutPrintWriter().println("Remote Exception: " + e);
         }
@@ -141,7 +145,8 @@
         } while (true);
 
         try {
-            int grammaticalGender = mBinderService.getSystemGrammaticalGender(userId);
+            int grammaticalGender = mBinderService.getSystemGrammaticalGender(mAttributionSource,
+                    userId);
             getOutPrintWriter().println(GRAMMATICAL_GENDER_MAP.get(grammaticalGender));
         } catch (RemoteException e) {
             getOutPrintWriter().println("Remote Exception: " + e);
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java
new file mode 100644
index 0000000..f056561
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.grammaticalinflection;
+
+import static android.Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER;
+
+import android.annotation.NonNull;
+import android.content.AttributionSource;
+import android.permission.PermissionManager;
+import android.util.Log;
+
+/**
+ * Utility methods for system grammatical gender.
+ */
+public class GrammaticalInflectionUtils {
+
+    private static final String TAG = "GrammaticalInflectionUtils";
+
+    public static boolean checkSystemGrammaticalGenderPermission(
+            @NonNull PermissionManager permissionManager,
+            @NonNull AttributionSource attributionSource) {
+        int permissionCheckResult = permissionManager.checkPermissionForDataDelivery(
+                READ_SYSTEM_GRAMMATICAL_GENDER,
+                attributionSource, /* message= */ null);
+        if (permissionCheckResult != PermissionManager.PERMISSION_GRANTED) {
+            Log.v(TAG, "AttributionSource: " + attributionSource
+                    + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index e827866..10c0186 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -140,7 +140,13 @@
                     wrapUpAndFinish();
                     return;
                 }
-
+                // Check if the action was finished before the callback was called.
+                // See {@link HdmiCecFeatureAction#finish}.
+                if (mState == STATE_NONE) {
+                    Slog.v(TAG, "Action was already finished before the callback was called.");
+                    wrapUpAndFinish();
+                    return;
+                }
                 Slog.v(TAG, "Device detected: " + ackedAddress);
                 allocateDevices(ackedAddress);
                 if (mDelayPeriod > 0) {
@@ -453,7 +459,6 @@
             wrapUpAndFinish();
             return;
         }
-
         // If finished current stage, move on to next stage.
         if (mProcessedDeviceCount == mDevices.size()) {
             mProcessedDeviceCount = 0;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 9087354..a79fb88 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -160,6 +160,9 @@
     // This variable is used for testing, in order to delay the logical address allocation.
     private long mLogicalAddressAllocationDelay = 0;
 
+    // This variable is used for testing, in order to delay polling devices.
+    private long mPollDevicesDelay = 0;
+
     // Private constructor.  Use HdmiCecController.create().
     private HdmiCecController(
             HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
@@ -463,6 +466,14 @@
     }
 
     /**
+     * This method is used for testing, in order to delay polling devices.
+     */
+    @VisibleForTesting
+    void setPollDevicesDelay(long delay) {
+        mPollDevicesDelay = delay;
+    }
+
+    /**
      * Returns true if the language code is well-formed.
      */
     @VisibleForTesting static boolean isLanguage(String language) {
@@ -523,7 +534,10 @@
         // Extract polling candidates. No need to poll against local devices.
         List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
         ArrayList<Integer> allocated = new ArrayList<>();
-        runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated);
+        mControlHandler.postDelayed(
+                () -> runDevicePolling(
+                        sourceAddress, pollingCandidates, retryCount, callback, allocated),
+                mPollDevicesDelay);
     }
 
     private List<Integer> pickPollCandidates(int pickStrategy) {
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
index 1153cc3..8a3a56c 100644
--- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.health;
 
+import static android.os.Flags.batteryPartStatusApi;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.health.BatteryHealthData;
@@ -150,6 +152,18 @@
                     healthData = service.getBatteryHealthData();
                     prop.setLong(healthData.batteryStateOfHealth);
                     break;
+                case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
+                    if (batteryPartStatusApi()) {
+                        healthData = service.getBatteryHealthData();
+                        prop.setString(healthData.batterySerialNumber);
+                    }
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
+                    if (batteryPartStatusApi()) {
+                        healthData = service.getBatteryHealthData();
+                        prop.setLong(healthData.batteryPartStatus);
+                    }
+                    break;
             }
         } catch (UnsupportedOperationException e) {
             // Leave prop untouched.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
index 2957935..a763251 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -77,8 +77,6 @@
             return this;
         }
 
-        // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
-        // documented more clearly.
         InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
             // If one or more auxiliary input methods are available, OK to stop populating the list.
             for (final InputMethodInfo imi : mInputMethodSet) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 4305808..834ba20 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -168,7 +168,7 @@
 
         final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodListLocked();
         if (imis.isEmpty()) {
-            return Collections.emptyList();
+            return new ArrayList<>();
         }
         if (isScreenLocked && includeAuxiliarySubtypes) {
             if (DEBUG) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 21e7bef..0ae6036 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -44,6 +44,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -318,11 +319,19 @@
 
     @Override
     public void onBindingDied(ComponentName name) {
-        if (DEBUG) {
-            Slog.d(TAG, this + ": Service binding died");
-        }
         unbind();
-        if (shouldBind()) {
+        if (Flags.enablePreventionOfKeepAliveRouteProviders()) {
+            Slog.w(
+                    TAG,
+                    TextUtils.formatSimple(
+                            "Route provider service (%s) binding died, but we did not rebind.",
+                            name.toString()));
+        } else if (shouldBind()) {
+            Slog.w(
+                    TAG,
+                    TextUtils.formatSimple(
+                            "Rebound to provider service (%s) after binding died.",
+                            name.toString()));
             bind();
         }
     }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index bd252e7..3d717a8 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -33,6 +33,8 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.media.flags.Flags;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -86,7 +88,9 @@
             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
             filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
             filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+            if (!Flags.enablePreventionOfKeepAliveRouteProviders()) {
+                filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+            }
             filter.addDataScheme("package");
             mContext.registerReceiverAsUser(mScanPackagesReceiver,
                     new UserHandle(mUserId), filter, null, mHandler);
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 71a6b5e..ab650af 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -16,7 +16,8 @@
 
 package com.android.server.notification;
 
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
 
 import android.app.UiModeManager;
 import android.app.WallpaperManager;
@@ -128,10 +129,9 @@
 
     private void updateNightModeImmediately(boolean useNightMode) {
         Binder.withCleanCallingIdentity(() -> {
-            // TODO: b/314285749 - Placeholder; use real APIs when available.
-            mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
-            mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
-                    useNightMode);
+            mUiModeManager.setAttentionModeThemeOverlay(
+                    useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT
+                            : MODE_ATTENTION_THEME_OVERLAY_OFF);
         });
     }
 
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 9ce3cb3..f6e7ef3 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -41,13 +41,14 @@
 import android.system.StructStat;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoParseException;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.BootReceiver;
 import com.android.server.ServiceThread;
 import com.android.server.os.TombstoneProtos.Cause;
 import com.android.server.os.TombstoneProtos.Tombstone;
-import com.android.server.os.protobuf.CodedInputStream;
 
 import libcore.io.IoUtils;
 
@@ -129,21 +130,18 @@
             return;
         }
 
+        String processName = "UNKNOWN";
         final boolean isProtoFile = filename.endsWith(".pb");
-        if (!isProtoFile) {
-            return;
-        }
+        File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
 
-        Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true);
+        Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
         if (parsedTombstone.isPresent()) {
-            BootReceiver.addTombstoneToDropBox(
-                    mContext, path, parsedTombstone.get().getTombstone(),
-                    parsedTombstone.get().getProcessName(), mTmpFileLock);
+            processName = parsedTombstone.get().getProcessName();
         }
+        BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
     }
 
-    private Optional<ParsedTombstone> handleProtoTombstone(
-            File path, boolean addToList) {
+    private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
         final String filename = path.getName();
         if (!filename.endsWith(".pb")) {
             Slog.w(TAG, "unexpected tombstone name: " + path);
@@ -173,7 +171,7 @@
             return Optional.empty();
         }
 
-        final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd);
+        final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
         if (!parsedTombstone.isPresent()) {
             IoUtils.closeQuietly(pfd);
             return Optional.empty();
@@ -186,7 +184,7 @@
                     previous.dispose();
                 }
 
-                mTombstones.put(number, parsedTombstone.get().getTombstoneFile());
+                mTombstones.put(number, parsedTombstone.get());
             }
         }
 
@@ -334,27 +332,6 @@
         }
     }
 
-    static class ParsedTombstone {
-        TombstoneFile mTombstoneFile;
-        Tombstone mTombstone;
-        ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) {
-            mTombstoneFile = tombstoneFile;
-            mTombstone = tombstone;
-        }
-
-        public String getProcessName() {
-            return mTombstoneFile.getProcessName();
-        }
-
-        public TombstoneFile getTombstoneFile() {
-            return mTombstoneFile;
-        }
-
-        public Tombstone getTombstone() {
-            return mTombstone;
-        }
-    }
-
     static class TombstoneFile {
         final ParcelFileDescriptor mPfd;
 
@@ -437,21 +414,67 @@
             }
         }
 
-        static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) {
-            Tombstone tombstoneProto;
-            try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) {
-                final byte[] tombstoneBytes = is.readAllBytes();
+        static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
+            final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
+            final ProtoInputStream stream = new ProtoInputStream(is);
 
-                tombstoneProto = Tombstone.parseFrom(
-                        CodedInputStream.newInstance(tombstoneBytes));
-            } catch (IOException ex) {
+            int pid = 0;
+            int uid = 0;
+            String processName = null;
+            String crashReason = "";
+            String selinuxLabel = "";
+
+            try {
+                while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    switch (stream.getFieldNumber()) {
+                        case (int) Tombstone.PID:
+                            pid = stream.readInt(Tombstone.PID);
+                            break;
+
+                        case (int) Tombstone.UID:
+                            uid = stream.readInt(Tombstone.UID);
+                            break;
+
+                        case (int) Tombstone.COMMAND_LINE:
+                            if (processName == null) {
+                                processName = stream.readString(Tombstone.COMMAND_LINE);
+                            }
+                            break;
+
+                        case (int) Tombstone.CAUSES:
+                            if (!crashReason.equals("")) {
+                                // Causes appear in decreasing order of likelihood. For now we only
+                                // want the most likely crash reason here, so ignore all others.
+                                break;
+                            }
+                            long token = stream.start(Tombstone.CAUSES);
+                        cause:
+                            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                                switch (stream.getFieldNumber()) {
+                                    case (int) Cause.HUMAN_READABLE:
+                                        crashReason = stream.readString(Cause.HUMAN_READABLE);
+                                        break cause;
+
+                                    default:
+                                        break;
+                                }
+                            }
+                            stream.end(token);
+                            break;
+
+                        case (int) Tombstone.SELINUX_LABEL:
+                            selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
+                            break;
+
+                        default:
+                            break;
+                    }
+                }
+            } catch (IOException | ProtoParseException ex) {
                 Slog.e(TAG, "Failed to parse tombstone", ex);
                 return Optional.empty();
             }
 
-            int pid = tombstoneProto.getPid();
-            int uid = tombstoneProto.getUid();
-
             if (!UserHandle.isApp(uid)) {
                 Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
                 return Optional.empty();
@@ -468,7 +491,6 @@
             final int userId = UserHandle.getUserId(uid);
             final int appId = UserHandle.getAppId(uid);
 
-            String selinuxLabel = tombstoneProto.getSelinuxLabel();
             if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
                 Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
                 return Optional.empty();
@@ -480,30 +502,11 @@
             result.mAppId = appId;
             result.mPid = pid;
             result.mUid = uid;
-            result.mProcessName = getCmdLineProcessName(tombstoneProto);
+            result.mProcessName = processName == null ? "" : processName;
             result.mTimestampMs = timestampMs;
-            result.mCrashReason = getCrashReason(tombstoneProto);
+            result.mCrashReason = crashReason;
 
-            return Optional.of(new ParsedTombstone(result, tombstoneProto));
-        }
-
-        private static String getCmdLineProcessName(Tombstone tombstoneProto) {
-            for (String cmdline : tombstoneProto.getCommandLineList()) {
-                if (cmdline != null) {
-                    return cmdline;
-                }
-            }
-            return "";
-        }
-
-        private static String getCrashReason(Tombstone tombstoneProto) {
-            for (Cause cause : tombstoneProto.getCausesList()) {
-                if (cause.getHumanReadable() != null
-                        && !cause.getHumanReadable().equals("")) {
-                    return cause.getHumanReadable();
-                }
-            }
-            return "";
+            return Optional.of(result);
         }
 
         public IParcelFileDescriptorRetriever getPfdRetriever() {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 9915554..ac826af 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -32,6 +32,8 @@
 import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
 import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
 
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
+
 import android.annotation.AppIdInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -56,7 +58,6 @@
 import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.Flags;
 import android.content.pm.ILauncherApps;
 import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.IPackageInstallerCallback;
@@ -507,7 +508,8 @@
             if (!canAccessProfile(userId, "cannot get shouldHideFromSuggestions")) {
                 return false;
             }
-            if (Flags.archiving() && packageName != null && isPackageArchived(packageName, user)) {
+            if (isArchivingEnabled() && packageName != null
+                    && isPackageArchived(packageName, user)) {
                 return true;
             }
             if (mPackageManagerInternal.filterAppAccess(
@@ -530,7 +532,7 @@
                                     .addCategory(Intent.CATEGORY_LAUNCHER)
                                     .setPackage(packageName),
                             user);
-            if (Flags.archiving()) {
+            if (isArchivingEnabled()) {
                 launcherActivities =
                         getActivitiesForArchivedApp(packageName, user, launcherActivities);
             }
@@ -701,7 +703,7 @@
                                 callingUid,
                                 user.getIdentifier());
                 if (activityInfo == null) {
-                    if (Flags.archiving()) {
+                    if (isArchivingEnabled()) {
                         return getMatchingArchivedAppActivityInfo(component, user);
                     }
                     return null;
@@ -984,7 +986,7 @@
                 long callingFlag =
                         PackageManager.MATCH_DIRECT_BOOT_AWARE
                                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-                if (Flags.archiving()) {
+                if (isArchivingEnabled()) {
                     callingFlag |= PackageManager.MATCH_ARCHIVED_PACKAGES;
                 }
                 final PackageInfo info =
@@ -1457,7 +1459,7 @@
             if (!canAccessProfile(user.getIdentifier(), "Cannot check component")) {
                 return false;
             }
-            if (Flags.archiving() && component != null && component.getPackageName() != null) {
+            if (isArchivingEnabled() && component != null && component.getPackageName() != null) {
                 List<LauncherActivityInfoInternal> archiveActivities =
                         generateLauncherActivitiesForArchivedApp(component.getPackageName(), user);
                 if (!archiveActivities.isEmpty()) {
@@ -1788,7 +1790,7 @@
                 }
                 if (!canLaunch
                         && includeArchivedApps
-                        && Flags.archiving()
+                        && isArchivingEnabled()
                         && getMatchingArchivedAppActivityInfo(component, user) != null) {
                     launchIntent.setPackage(null);
                     launchIntent.setComponent(component);
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index b18f2bf..09a91ed 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -53,11 +53,11 @@
 import android.content.pm.ArchivedActivityParcel;
 import android.content.pm.ArchivedPackageInfo;
 import android.content.pm.ArchivedPackageParcel;
+import android.content.pm.Flags;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.DeleteFlags;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.VersionedPackage;
@@ -78,6 +78,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SELinux;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ExceptionUtils;
@@ -173,12 +174,15 @@
         return userState.getArchiveState() != null && !userState.isInstalled();
     }
 
+    public static boolean isArchivingEnabled() {
+        return Flags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false);
+    }
+
     void requestArchive(
             @NonNull String packageName,
             @NonNull String callerPackageName,
             @NonNull IntentSender intentSender,
-            @NonNull UserHandle userHandle,
-            @DeleteFlags int flags) {
+            @NonNull UserHandle userHandle) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(callerPackageName);
         Objects.requireNonNull(intentSender);
@@ -218,7 +222,7 @@
                                     new VersionedPackage(packageName,
                                             PackageManager.VERSION_CODE_HIGHEST),
                                     callerPackageName,
-                                    DELETE_ARCHIVE | DELETE_KEEP_DATA | flags,
+                                    DELETE_ARCHIVE | DELETE_KEEP_DATA,
                                     intentSender,
                                     userId,
                                     binderUid);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index fdcd28b..7bf9fe7a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -30,6 +30,7 @@
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
 import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -826,7 +827,7 @@
         }
 
         params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE;
-        if (Flags.archiving() && params.appPackageName != null) {
+        if (isArchivingEnabled() && params.appPackageName != null) {
             PackageStateInternal ps = mPm.snapshotComputer().getPackageStateInternal(
                     params.appPackageName, SYSTEM_UID);
             if (ps != null
@@ -1034,7 +1035,7 @@
     private int getExistingDraftSessionIdInternal(int installerUid,
             SessionParams sessionParams, int userId) {
         String appPackageName = sessionParams.appPackageName;
-        if (!Flags.archiving() || installerUid == INVALID_UID || appPackageName == null) {
+        if (!isArchivingEnabled() || installerUid == INVALID_UID || appPackageName == null) {
             return SessionInfo.INVALID_ID;
         }
 
@@ -1407,14 +1408,11 @@
         final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
                 statusReceiver, versionedPackage.getPackageName(),
                 canSilentlyInstallPackage, userId, mPackageArchiver, flags);
-        final boolean shouldShowConfirmationDialog =
-                (flags & PackageManager.DELETE_SHOW_DIALOG) != 0;
-        if (!shouldShowConfirmationDialog
-                && mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
-                    == PackageManager.PERMISSION_GRANTED) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
+                == PackageManager.PERMISSION_GRANTED) {
             // Sweet, call straight through!
             mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
-        } else if (!shouldShowConfirmationDialog && canSilentlyInstallPackage) {
+        } else if (canSilentlyInstallPackage) {
             // Allow the device owner and affiliated profile owner to silently delete packages
             // Need to clear the calling identity to get DELETE_PACKAGES permission
             final long ident = Binder.clearCallingIdentity();
@@ -1656,10 +1654,8 @@
             @NonNull String packageName,
             @NonNull String callerPackageName,
             @NonNull IntentSender intentSender,
-            @NonNull UserHandle userHandle,
-            @DeleteFlags int flags) {
-        mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender,
-                userHandle, flags);
+            @NonNull UserHandle userHandle) {
+        mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender, userHandle);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 5724ee0..ca00c84 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4645,7 +4645,7 @@
         try {
             mInterface.getPackageInstaller().requestArchive(packageName,
                     /* callerPackageName= */ "", receiver.getIntentSender(),
-                    new UserHandle(translatedUserId), 0);
+                    new UserHandle(translatedUserId));
         } catch (Exception e) {
             pw.println("Failure [" + e.getMessage() + "]");
             return 1;
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 3e3b72c..70352be 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -392,8 +392,7 @@
             // Delete from mSettings
             final SparseBooleanArray changedUsers = new SparseBooleanArray();
             synchronized (mPm.mLock) {
-                mPm.mSettings.removePackageLPw(packageName);
-                outInfo.mIsAppIdRemoved = true;
+                outInfo.mIsAppIdRemoved = mPm.mSettings.removePackageAndAppIdLPw(packageName);
                 if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
                     final SharedUserSetting sus = mPm.mSettings.getSharedUserSettingLPr(deletedPs);
                     // If we don't have a disabled system package to reinstall, the package is
@@ -485,8 +484,6 @@
         synchronized (mPm.mInstallLock) {
             cleanUpResourcesLI(codeFile, instructionSets);
         }
-        // TODO: open logging to help debug, will delete or add debug flag
-        Slog.d(TAG, "cleanUpResources for " + codeFile);
         if (packageName == null) {
             return;
         }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b286b12..a97652c 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -92,7 +92,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedPermission;
@@ -1460,22 +1459,28 @@
         return false;
     }
 
-    int removePackageLPw(String name) {
+
+    /**
+     * Remove package from mPackages and its corresponding AppId.
+     *
+     * @return True if the AppId has been removed.
+     * False if the app doesn't exist, or if the app has a shared UID and there are other apps that
+     * still use the same shared UID even after the target app is removed.
+     */
+    boolean removePackageAndAppIdLPw(String name) {
         final PackageSetting p = mPackages.remove(name);
         if (p != null) {
             removeInstallerPackageStatus(name);
             SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(p);
             if (sharedUserSetting != null) {
                 sharedUserSetting.removePackage(p);
-                if (checkAndPruneSharedUserLPw(sharedUserSetting, false)) {
-                    return sharedUserSetting.mAppId;
-                }
+                return checkAndPruneSharedUserLPw(sharedUserSetting, false);
             } else {
                 removeAppIdLPw(p.getAppId());
-                return p.getAppId();
+                return true;
             }
         }
-        return -1;
+        return false;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7b0a69b..c94111c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
@@ -41,6 +43,7 @@
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.StringRes;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -74,8 +77,10 @@
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.multiuser.Flags;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -83,6 +88,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IProgressListener;
 import android.os.IUserManager;
@@ -91,6 +97,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -174,6 +181,8 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -296,6 +305,12 @@
 
     private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
 
+    /**
+     * The time duration (in milliseconds) post device inactivity after which the private space
+     * should be auto-locked if the corresponding settings option is selected by the user.
+     */
+    private static final long PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000;
+
     // Tron counters
     private static final String TRON_GUEST_CREATED = "users_guest_created";
     private static final String TRON_USER_CREATED = "users_user_created";
@@ -321,6 +336,8 @@
 
     private final Handler mHandler;
 
+    private final ThreadPoolExecutor mInternalExecutor;
+
     private final File mUsersDir;
     private final File mUserListFile;
 
@@ -522,6 +539,36 @@
 
     private final LockPatternUtils mLockPatternUtils;
 
+    private KeyguardManager.KeyguardLockedStateListener mKeyguardLockedStateListener;
+
+    /** Token to identify and remove already scheduled private space auto-lock messages */
+    private static final Object PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN = new Object();
+
+    /** Content observer to get callbacks for privte space autolock settings changes */
+    private final SettingsObserver mPrivateSpaceAutoLockSettingsObserver;
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (isAutoLockForPrivateSpaceEnabled()) {
+                final String path = uri.getLastPathSegment();
+                if (TextUtils.equals(path, Settings.Secure.PRIVATE_SPACE_AUTO_LOCK)) {
+                    int autoLockPreference =
+                            Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+                                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+                                    getMainUserIdUnchecked());
+                    Slog.i(LOG_TAG, "Auto-lock settings changed to " + autoLockPreference);
+                    setOrUpdateAutoLockPreferenceForPrivateProfile(autoLockPreference);
+                }
+            }
+        }
+    }
+
     private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
             "com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK";
 
@@ -534,12 +581,168 @@
             final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT, android.content.IntentSender.class);
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
             final String callingPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-            // Call setQuietModeEnabled on bg thread to avoid ANR
-            BackgroundThread.getHandler().post(() ->
-                    setQuietModeEnabled(userId, false, target, callingPackage));
+            setQuietModeEnabledAsync(userId, false, target, callingPackage);
         }
     };
 
+    /** Checks if the device inactivity broadcast receiver is already registered*/
+    private boolean mIsDeviceInactivityBroadcastReceiverRegistered = false;
+
+    private final BroadcastReceiver mDeviceInactivityBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (isAutoLockForPrivateSpaceEnabled()) {
+                if (ACTION_SCREEN_OFF.equals(intent.getAction())) {
+                    maybeScheduleMessageToAutoLockPrivateSpace();
+                } else if (ACTION_SCREEN_ON.equals(intent.getAction())) {
+                    // Remove any queued messages since the device is interactive again
+                    mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
+                }
+            }
+        }
+    };
+
+    @VisibleForTesting
+    void maybeScheduleMessageToAutoLockPrivateSpace() {
+        // No action needed if auto-lock on inactivity not selected
+        int privateSpaceAutoLockPreference =
+                Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                        Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+                        Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+                        getMainUserIdUnchecked());
+        if (privateSpaceAutoLockPreference
+                != Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+            return;
+        }
+        int privateProfileUserId = getPrivateProfileUserId();
+        if (privateProfileUserId != UserHandle.USER_NULL) {
+            scheduleMessageToAutoLockPrivateSpace(privateProfileUserId,
+                    PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN,
+                    PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS);
+        }
+    }
+
+    @VisibleForTesting
+    void scheduleMessageToAutoLockPrivateSpace(int userId, Object token,
+            long delayInMillis) {
+        mHandler.postDelayed(() -> {
+            final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+            if (powerManager != null && !powerManager.isInteractive()) {
+                Slog.i(LOG_TAG, "Auto-locking private space with user-id " + userId);
+                setQuietModeEnabledAsync(userId, true,
+                        /* target */ null, mContext.getPackageName());
+            } else {
+                Slog.i(LOG_TAG, "Device is interactive, skipping auto-lock");
+            }
+        }, token, delayInMillis);
+    }
+
+    @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+    private void initializeAndRegisterKeyguardLockedStateListener() {
+        mKeyguardLockedStateListener = this::tryAutoLockingPrivateSpaceOnKeyguardChanged;
+        // Register with keyguard to send locked state events to the listener initialized above
+        try {
+            final KeyguardManager keyguardManager =
+                    mContext.getSystemService(KeyguardManager.class);
+            Slog.i(LOG_TAG, "Adding keyguard locked state listener");
+            keyguardManager.addKeyguardLockedStateListener(new HandlerExecutor(mHandler),
+                    mKeyguardLockedStateListener);
+        } catch (Exception e) {
+            Slog.e(LOG_TAG, "Error adding keyguard locked listener ", e);
+        }
+    }
+
+    @VisibleForTesting
+    @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+    void setOrUpdateAutoLockPreferenceForPrivateProfile(
+            @Settings.Secure.PrivateSpaceAutoLockOption int autoLockPreference) {
+        int privateProfileUserId = getPrivateProfileUserId();
+        if (privateProfileUserId == UserHandle.USER_NULL) {
+            Slog.e(LOG_TAG, "Auto-lock preference updated but private space user not found");
+            return;
+        }
+
+        if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+            // Register inactivity broadcast
+            if (!mIsDeviceInactivityBroadcastReceiverRegistered) {
+                Slog.i(LOG_TAG, "Registering device inactivity broadcast receivers");
+                mContext.registerReceiver(mDeviceInactivityBroadcastReceiver,
+                        new IntentFilter(ACTION_SCREEN_OFF),
+                        null, mHandler);
+
+                mContext.registerReceiver(mDeviceInactivityBroadcastReceiver,
+                        new IntentFilter(ACTION_SCREEN_ON),
+                        null, mHandler);
+
+                mIsDeviceInactivityBroadcastReceiverRegistered = true;
+            }
+        } else {
+            // Unregister device inactivity broadcasts
+            if (mIsDeviceInactivityBroadcastReceiverRegistered) {
+                Slog.i(LOG_TAG, "Removing device inactivity broadcast receivers");
+                mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
+                mContext.unregisterReceiver(mDeviceInactivityBroadcastReceiver);
+                mIsDeviceInactivityBroadcastReceiverRegistered = false;
+            }
+        }
+
+        if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK) {
+            // Initialize and add keyguard state listener
+            initializeAndRegisterKeyguardLockedStateListener();
+        } else {
+            // Remove keyguard state listener
+            try {
+                final KeyguardManager keyguardManager =
+                        mContext.getSystemService(KeyguardManager.class);
+                Slog.i(LOG_TAG, "Removing keyguard locked state listener");
+                keyguardManager.removeKeyguardLockedStateListener(mKeyguardLockedStateListener);
+            } catch (Exception e) {
+                Slog.e(LOG_TAG, "Error adding keyguard locked state listener ", e);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void tryAutoLockingPrivateSpaceOnKeyguardChanged(boolean isKeyguardLocked) {
+        if (isAutoLockForPrivateSpaceEnabled()) {
+            int autoLockPreference = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+                    getMainUserIdUnchecked());
+            boolean isAutoLockOnDeviceLockSelected =
+                    autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK;
+            if (isKeyguardLocked && isAutoLockOnDeviceLockSelected) {
+                int privateProfileUserId = getPrivateProfileUserId();
+                if (privateProfileUserId != UserHandle.USER_NULL) {
+                    Slog.i(LOG_TAG, "Auto-locking private space with user-id "
+                            + privateProfileUserId);
+                    setQuietModeEnabledAsync(privateProfileUserId,
+                            /* enableQuietMode */true, /* target */ null,
+                            mContext.getPackageName());
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void setQuietModeEnabledAsync(@UserIdInt int userId, boolean enableQuietMode,
+            IntentSender target, @Nullable String callingPackage) {
+        if (android.multiuser.Flags.moveQuietModeOperationsToSeparateThread()) {
+            // Call setQuietModeEnabled on a separate thread. Calling this operation on the main
+            // thread can cause ANRs, posting on a BackgroundThread can result in delays
+            Slog.d(LOG_TAG, "Calling setQuietModeEnabled for user " + userId
+                    + " on a separate thread");
+            mInternalExecutor.execute(() -> setQuietModeEnabled(userId, enableQuietMode, target,
+                    callingPackage));
+        } else {
+            // Call setQuietModeEnabled on bg thread to avoid ANR
+            BackgroundThread.getHandler().post(
+                    () -> setQuietModeEnabled(userId, enableQuietMode, target,
+                            callingPackage)
+            );
+        }
+    }
+
     /**
      * Cache the owner name string, since it could be read repeatedly on a critical code path
      * but hit by slow IO. This could be eliminated once we have the cached UserInfo in place.
@@ -766,6 +969,8 @@
         mPackagesLock = packagesLock;
         mUsers = users != null ? users : new SparseArray<>();
         mHandler = new MainHandler();
+        mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1,
+                /* keepAliveTime */ 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
         mUserVisibilityMediator = new UserVisibilityMediator(mHandler);
         mUserDataPreparer = userDataPreparer;
         mUserTypes = UserTypeFactory.getUserTypes();
@@ -790,9 +995,15 @@
         mLockPatternUtils = new LockPatternUtils(mContext);
         mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
         mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
+        mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler);
         emulateSystemUserModeIfNeeded();
     }
 
+    private static boolean isAutoLockForPrivateSpaceEnabled() {
+        return android.os.Flags.allowPrivateProfile()
+                && Flags.supportAutolockForPrivateSpace();
+    }
+
     void systemReady() {
         mAppOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -809,6 +1020,22 @@
                 new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED),
                 null, mHandler);
 
+        if (isAutoLockForPrivateSpaceEnabled()) {
+
+            int mainUserId = getMainUserIdUnchecked();
+            if (mainUserId != UserHandle.USER_NULL) {
+                mContext.getContentResolver().registerContentObserverAsUser(
+                        Settings.Secure.getUriFor(
+                                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), false,
+                        mPrivateSpaceAutoLockSettingsObserver, UserHandle.of(mainUserId));
+
+                setOrUpdateAutoLockPreferenceForPrivateProfile(
+                        Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+                                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, mainUserId));
+            }
+        }
+
         markEphemeralUsersForRemoval();
     }
 
@@ -973,6 +1200,18 @@
         return UserHandle.USER_NULL;
     }
 
+    private @UserIdInt int getPrivateProfileUserId() {
+        synchronized (mUsersLock) {
+            for (int userId : getUserIds()) {
+                UserInfo userInfo = getUserInfoLU(userId);
+                if (userInfo != null && userInfo.isPrivateProfile()) {
+                    return userInfo.id;
+                }
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
     @Override
     public void setBootUser(@UserIdInt int userId) {
         checkCreateUsersPermission("Set boot user");
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 375ef61..a4dbce6 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -65,6 +65,7 @@
 import com.android.internal.widget.RebootEscrowListener;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.Watchdog;
 import com.android.server.pm.ApexManager;
 import com.android.server.recoverysystem.hal.BootControlHIDL;
 
@@ -112,6 +113,9 @@
 
     private static final int SOCKET_CONNECTION_MAX_RETRY = 30;
 
+    /** How long to pause the watchdog for when rebooting the device. */
+    private static final int REBOOT_WATCHDOG_PAUSE_DURATION_MS = 20_000;
+
     static final String REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX = "_request_lskf_timestamp";
     static final String REQUEST_LSKF_COUNT_PREF_SUFFIX = "_request_lskf_count";
 
@@ -899,7 +903,8 @@
 
         // Clear the metrics prefs after a successful RoR reboot.
         mInjector.getMetricsPrefs().deletePrefsFile();
-
+        Watchdog.getInstance().pauseWatchingCurrentThreadFor(
+                REBOOT_WATCHDOG_PAUSE_DURATION_MS, "reboot can be slow");
         PowerManager pm = mInjector.getPowerManager();
         pm.reboot(reason);
         return RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIED;
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index e3eb5b5..e5a8a6d 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -44,6 +44,9 @@
 import android.graphics.drawable.Drawable;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -155,6 +158,8 @@
     private final LockPatternUtils mLockPatternUtils;
     private final UserManager mUserManager;
     private final ActivityManager mActivityManager;
+    private FingerprintManager mFingerprintManager;
+    private FaceManager mFaceManager;
     private VirtualDeviceManagerInternal mVirtualDeviceManager;
 
     private enum TrustState {
@@ -292,6 +297,8 @@
             mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
             mReceiver.register(mContext);
             mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
+            mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+            mFaceManager = mContext.getSystemService(FaceManager.class);
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             mTrustAgentsCanRun = true;
             refreshAgentList(UserHandle.USER_ALL);
@@ -893,7 +900,19 @@
 
     private void notifyKeystoreOfDeviceLockState(int userId, boolean isLocked) {
         if (isLocked) {
-            Authorization.onDeviceLocked(userId, getBiometricSids(userId));
+            if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()) {
+                // A profile with unified challenge is unlockable not by its own biometrics and
+                // trust agents, but rather by those of the parent user.  Therefore, when protecting
+                // the profile's UnlockedDeviceRequired keys, we must use the parent's list of
+                // biometric SIDs and weak unlock methods, not the profile's.
+                int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
+                        ? resolveProfileParent(userId) : userId;
+
+                Authorization.onDeviceLocked(userId, getBiometricSids(authUserId),
+                        isWeakUnlockMethodEnabled(authUserId));
+            } else {
+                Authorization.onDeviceLocked(userId, getBiometricSids(userId), false);
+            }
         } else {
             // Notify Keystore that the device is now unlocked for the user.  Note that for unlocks
             // with LSKF, this is redundant with the call from LockSettingsService which provides
@@ -1440,16 +1459,59 @@
         if (biometricManager == null) {
             return new long[0];
         }
-        if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()
-                && mLockPatternUtils.isProfileWithUnifiedChallenge(userId)) {
-            // Profiles with unified challenge have their own set of biometrics, but the device
-            // unlock happens via the parent user.  In this case Keystore needs to be given the list
-            // of biometric SIDs from the parent user, not the profile.
-            userId = resolveProfileParent(userId);
-        }
         return biometricManager.getAuthenticatorIds(userId);
     }
 
+    // Returns whether the device can become unlocked for the specified user via one of that user's
+    // non-strong biometrics or trust agents.  This assumes that the device is currently locked, or
+    // is becoming locked, for the user.
+    private boolean isWeakUnlockMethodEnabled(int userId) {
+
+        // Check whether the system currently allows the use of non-strong biometrics for the user,
+        // *and* the user actually has a non-strong biometric enrolled.
+        //
+        // The biometrics framework ostensibly supports multiple sensors per modality.  However,
+        // that feature is unused and untested.  So, we simply consider one sensor per modality.
+        //
+        // Also, currently we just consider fingerprint and face, matching Keyguard.  If Keyguard
+        // starts supporting other biometric modalities, this will need to be updated.
+        if (mStrongAuthTracker.isBiometricAllowedForUser(/* isStrongBiometric= */ false, userId)) {
+            DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
+            int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userId);
+
+            if (mFingerprintManager != null
+                    && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) == 0
+                    && mFingerprintManager.hasEnrolledTemplates(userId)
+                    && isWeakOrConvenienceSensor(
+                            mFingerprintManager.getSensorProperties().get(0))) {
+                Slog.i(TAG, "User is unlockable by non-strong fingerprint auth");
+                return true;
+            }
+
+            if (mFaceManager != null
+                    && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FACE) == 0
+                    && mFaceManager.hasEnrolledTemplates(userId)
+                    && isWeakOrConvenienceSensor(mFaceManager.getSensorProperties().get(0))) {
+                Slog.i(TAG, "User is unlockable by non-strong face auth");
+                return true;
+            }
+        }
+
+        // Check whether it's possible for the device to be actively unlocked by a trust agent.
+        if (getUserTrustStateInner(userId) == TrustState.TRUSTABLE
+                || (isAutomotive() && isTrustUsuallyManagedInternal(userId))) {
+            Slog.i(TAG, "User is unlockable by trust agent");
+            return true;
+        }
+
+        return false;
+    }
+
+    private static boolean isWeakOrConvenienceSensor(SensorProperties sensor) {
+        return sensor.getSensorStrength() == SensorProperties.STRENGTH_WEAK
+                || sensor.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE;
+    }
+
     // User lifecycle
 
     @Override
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index d903ad4..73fc8e9 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2280,6 +2280,26 @@
         }
 
         @Override
+        public void setVideoFrozen(IBinder sessionToken, boolean isFrozen, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "setVideoFrozen");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .setVideoFrozen(isFrozen);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in setVideoFrozen", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void setTvMessageEnabled(IBinder sessionToken, int type, boolean enabled,
                 int userId) {
             final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index e3aba0f..c15ac37 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -111,7 +111,7 @@
      * but it can be changed for local testing.
      */
     private static boolean anrTimerServiceEnabled() {
-        return Flags.anrTimerServiceEnabled();
+        return Flags.anrTimerService();
     }
 
     /**
diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig
index 489e21a..163116b 100644
--- a/services/core/java/com/android/server/utils/flags.aconfig
+++ b/services/core/java/com/android/server/utils/flags.aconfig
@@ -1,7 +1,7 @@
 package: "com.android.server.utils"
 
 flag {
-     name: "anr_timer_service_enabled"
+     name: "anr_timer_service"
      namespace: "system_performance"
      is_fixed_read_only: true
      description: "Feature flag for the ANR timer service"
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3782b42..26584315 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1256,7 +1256,7 @@
                         // migrateStaticSystemToLockWallpaperLocked() and added to the lock wp map
                         // if successful.
                         WallpaperData lockWp = mLockWallpaperMap.get(mNewWallpaper.userId);
-                        if (lockWp != null) {
+                        if (lockWp != null && mOriginalSystem.connection != null) {
                             // Successful rename, set old system+lock to the pending lock wp
                             if (DEBUG) {
                                 Slog.v(TAG, "static system+lock to system success");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 13f7152..f6d77ea 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -60,6 +60,7 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
@@ -104,7 +105,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.AuxiliaryResolveInfo;
-import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
@@ -1034,7 +1034,7 @@
         }
 
         if (err == ActivityManager.START_SUCCESS && aInfo == null) {
-            if (Flags.archiving()) {
+            if (isArchivingEnabled()) {
                 PackageArchiver packageArchiver = mService
                         .getPackageManagerInternalLocked()
                         .getPackageArchiver();
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 9c9cf04..0f36d8e 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -18,6 +18,10 @@
 
 import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.app.ComponentOptions.BackgroundActivityStartMode;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -31,6 +35,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
 import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
 import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
+import static com.android.window.flags.Flags.balRequireOptInSameUid;
 import static com.android.window.flags.Flags.balShowToasts;
 import static com.android.window.flags.Flags.balShowToastsBlocked;
 import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
@@ -94,9 +99,9 @@
     public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
             ActivityOptions.makeBasic()
                     .setPendingIntentBackgroundActivityStartMode(
-                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)
+                            MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)
                     .setPendingIntentCreatorBackgroundActivityStartMode(
-                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+                            MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
 
     private final ActivityTaskManagerService mService;
 
@@ -240,6 +245,7 @@
         private final WindowProcessController mRealCallerApp;
         private final boolean mIsCallForResult;
         private final ActivityOptions mCheckedOptions;
+        private final String mAutoOptInReason;
         private BalVerdict mResultForCaller;
         private BalVerdict mResultForRealCaller;
 
@@ -263,28 +269,38 @@
             mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
             mIsCallForResult = resultRecord != null;
             mCheckedOptions = checkedOptions;
-            if (balRequireOptInByPendingIntentCreator() // auto-opt in introduced with this feature
-                    && (originatingPendingIntent == null // not a PendingIntent
-                    || mIsCallForResult) // sent for result
-            ) {
+            @BackgroundActivityStartMode int callerBackgroundActivityStartMode =
+                    checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode();
+            @BackgroundActivityStartMode int realCallerBackgroundActivityStartMode =
+                    checkedOptions.getPendingIntentBackgroundActivityStartMode();
+
+            if (balRequireOptInByPendingIntentCreator() && originatingPendingIntent == null) {
+                mAutoOptInReason = "notPendingIntent";
+            } else if (balRequireOptInByPendingIntentCreator() && mIsCallForResult) {
+                mAutoOptInReason = "callForResult";
+            } else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
+                mAutoOptInReason = "sameUid";
+            } else {
+                mAutoOptInReason = null;
+            }
+
+            if (mAutoOptInReason != null) {
                 // grant BAL privileges unless explicitly opted out
                 mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
-                        checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+                        callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED
                                 ? BackgroundStartPrivileges.NONE
                                 : BackgroundStartPrivileges.ALLOW_BAL;
-                mBalAllowedByPiSender =
-                        checkedOptions.getPendingIntentBackgroundActivityStartMode()
-                                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
-                                ? BackgroundStartPrivileges.NONE
-                                : BackgroundStartPrivileges.ALLOW_BAL;
+                mBalAllowedByPiSender = realCallerBackgroundActivityStartMode
+                        == MODE_BACKGROUND_ACTIVITY_START_DENIED
+                        ? BackgroundStartPrivileges.NONE
+                        : BackgroundStartPrivileges.ALLOW_BAL;
             } else {
                 // for PendingIntents we restrict BAL based on target_sdk
                 mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator(
                         callingUid, callingPackage, checkedOptions);
                 final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening =
-                        checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+                        callerBackgroundActivityStartMode
+                                == MODE_BACKGROUND_ACTIVITY_START_DENIED
                                 ? BackgroundStartPrivileges.NONE
                                 : BackgroundStartPrivileges.ALLOW_BAL;
                 mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator()
@@ -326,11 +342,11 @@
         private BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCreator(
                 int callingUid, String callingPackage, ActivityOptions checkedOptions) {
             switch (checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()) {
-                case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+                case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
                     return BackgroundStartPrivileges.ALLOW_BAL;
-                case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED:
+                case MODE_BACKGROUND_ACTIVITY_START_DENIED:
                     return BackgroundStartPrivileges.NONE;
-                case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
+                case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
                     // no explicit choice by the app - let us decide what to do
                     if (callingPackage != null) {
                         // determine based on the calling/creating package
@@ -431,6 +447,7 @@
             sb.append("; hasRealCaller: ").append(hasRealCaller());
             sb.append("; isCallForResult: ").append(mIsCallForResult);
             sb.append("; isPendingIntent: ").append(isPendingIntent());
+            sb.append("; autoOptInReason: ").append(mAutoOptInReason);
             if (hasRealCaller()) {
                 sb.append("; realCallingPackage: ")
                         .append(getDebugPackageName(mRealCallingPackage, mRealCallingUid));
@@ -456,6 +473,50 @@
             sb.append("]");
             return sb.toString();
         }
+
+        public boolean isPendingIntentBalAllowedByPermission() {
+            return PendingIntentRecord.isPendingIntentBalAllowedByPermission(mCheckedOptions);
+        }
+
+        public boolean callerExplicitOptInOrAutoOptIn() {
+            if (mAutoOptInReason == null) {
+                return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                        == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+            } else {
+                return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                        != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+            }
+        }
+
+        public boolean realCallerExplicitOptInOrAutoOptIn() {
+            if (mAutoOptInReason == null) {
+                return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                        == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+            } else {
+                return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                        != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+            }
+        }
+
+        public boolean callerExplicitOptOut() {
+            return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                    == MODE_BACKGROUND_ACTIVITY_START_DENIED;
+        }
+
+        public boolean realCallerExplicitOptOut() {
+            return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                    == MODE_BACKGROUND_ACTIVITY_START_DENIED;
+        }
+
+        public boolean callerExplicitOptInOrOut() {
+            return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                    != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+        }
+
+        public boolean realCallerExplicitOptInOrOut() {
+            return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                    != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+        }
     }
 
     static class BalVerdict {
@@ -624,7 +685,7 @@
         // PendingIntents is null).
         BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
                 ? resultForCaller
-                : checkBackgroundActivityStartAllowedBySender(state, checkedOptions)
+                : checkBackgroundActivityStartAllowedBySender(state)
                         .setBasedOnRealCaller();
         state.setResultForRealCaller(resultForRealCaller);
 
@@ -634,18 +695,14 @@
         }
 
         // Handle cases with explicit opt-in
-        if (resultForCaller.allows()
-                && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+        if (resultForCaller.allows() && state.callerExplicitOptInOrAutoOptIn()) {
             if (DEBUG_ACTIVITY_STARTS) {
                 Slog.d(TAG, "Activity start explicitly allowed by caller. "
                         + state.dump());
             }
             return allowBasedOnCaller(state);
         }
-        if (resultForRealCaller.allows()
-                && checkedOptions.getPendingIntentBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+        if (resultForRealCaller.allows() && state.realCallerExplicitOptInOrAutoOptIn()) {
             if (DEBUG_ACTIVITY_STARTS) {
                 Slog.d(TAG, "Activity start explicitly allowed by real caller. "
                         + state.dump());
@@ -653,12 +710,9 @@
             return allowBasedOnRealCaller(state);
         }
         // Handle PendingIntent cases with default behavior next
-        boolean callerCanAllow = resultForCaller.allows()
-                && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+        boolean callerCanAllow = resultForCaller.allows() && !state.callerExplicitOptOut();
         boolean realCallerCanAllow = resultForRealCaller.allows()
-                && checkedOptions.getPendingIntentBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+                && !state.realCallerExplicitOptOut();
         if (callerCanAllow && realCallerCanAllow) {
             // Both caller and real caller allow with system defined behavior
             if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
@@ -880,11 +934,9 @@
      * @return A code denoting which BAL rule allows an activity to be started,
      * or {@link #BAL_BLOCK} if the launch should be blocked
      */
-    BalVerdict checkBackgroundActivityStartAllowedBySender(
-            BalState state,
-            ActivityOptions checkedOptions) {
+    BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) {
 
-        if (PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions)
+        if (state.isPendingIntentBalAllowedByPermission()
                 && ActivityManager.checkComponentPermission(
                 android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
                 state.mRealCallingUid, NO_PROCESS_UID, true) == PackageManager.PERMISSION_GRANTED) {
@@ -1545,13 +1597,11 @@
                 state.mRealCallingUid,
                 state.mResultForCaller == null ? BAL_BLOCK : state.mResultForCaller.getRawCode(),
                 state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts(),
-                state.mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                        != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
+                state.callerExplicitOptInOrOut(),
                 state.mResultForRealCaller == null ? BAL_BLOCK
                         : state.mResultForRealCaller.getRawCode(),
                 state.mBalAllowedByPiSender.allowsBackgroundActivityStarts(),
-                state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
-                        != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED
+                state.realCallerExplicitOptInOrOut()
         );
     }
 
diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
index 3862b82..a7d6903b 100644
--- a/services/core/java/com/android/server/wm/SensitiveContentPackages.java
+++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
@@ -21,7 +21,6 @@
 
 import java.io.PrintWriter;
 import java.util.Objects;
-import java.util.Set;
 
 /**
  * Cache of distinct package/uid pairs that require being blocked from screen capture. This class is
@@ -41,10 +40,33 @@
         return false;
     }
 
-    /** Replaces the set of package/uid pairs to set that should be blocked from screen capture */
-    public void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos) {
-        mProtectedPackages.clear();
+    /**
+     * Adds the set of package/uid pairs to set that should be blocked from screen capture
+     *
+     * @param packageInfos packages to be blocked
+     * @return {@code true} if packages set is modified, {@code false} otherwise.
+     */
+    public boolean addBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos) {
+        if (mProtectedPackages.equals(packageInfos)) {
+            // new set is equal to current set of packages, no need to update
+            return false;
+        }
         mProtectedPackages.addAll(packageInfos);
+        return true;
+    }
+
+    /**
+     * Clears the set of package/uid pairs that should be blocked from screen capture
+     *
+     * @return {@code true} if packages set is modified, {@code false} otherwise.
+     */
+    public boolean clearBlockedApps() {
+        if (mProtectedPackages.isEmpty()) {
+            // set was already empty
+            return false;
+        }
+        mProtectedPackages.clear();
+        return true;
     }
 
     void dump(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 314d720..8f9ed83 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3558,9 +3558,11 @@
                 && top.mLetterboxUiController.isSystemOverrideToFullscreenEnabled();
         appCompatTaskInfo.isFromLetterboxDoubleTap = top != null
                 && top.mLetterboxUiController.isFromDoubleTap();
-        if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+        if (top != null) {
             appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width();
             appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height();
+        }
+        if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
             if (appCompatTaskInfo.isTopActivityPillarboxed()) {
                 // Pillarboxed
                 appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 22b690e..f2a58e5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Message;
+import android.util.ArraySet;
 import android.util.Pair;
 import android.view.ContentRecordingSession;
 import android.view.Display;
@@ -1020,5 +1021,13 @@
      *
      * @param packageInfos set of {@link PackageInfo} whose windows should be blocked from capture
      */
-    public abstract void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos);
+    public abstract void addBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos);
+
+    /**
+     * Clears apps added to collection of apps in which screen capture should be disabled.
+     *
+     * <p> This clears and resets any existing set or added applications from
+     * * {@link #addBlockScreenCaptureForApps(ArraySet)}
+     */
+    public abstract void clearBlockedApps();
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9179acf..6c83356 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -370,7 +370,6 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
@@ -8576,10 +8575,23 @@
         }
 
         @Override
-        public void setShouldBlockScreenCaptureForApp(Set<PackageInfo> packageInfos) {
+        public void addBlockScreenCaptureForApps(ArraySet<PackageInfo> packageInfos) {
             synchronized (mGlobalLock) {
-                mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(packageInfos);
-                WindowManagerService.this.refreshScreenCaptureDisabled();
+                boolean modified =
+                        mSensitiveContentPackages.addBlockScreenCaptureForApps(packageInfos);
+                if (modified) {
+                    WindowManagerService.this.refreshScreenCaptureDisabled();
+                }
+            }
+        }
+
+        @Override
+        public void clearBlockedApps() {
+            synchronized (mGlobalLock) {
+                boolean modified = mSensitiveContentPackages.clearBlockedApps();
+                if (modified) {
+                    WindowManagerService.this.refreshScreenCaptureDisabled();
+                }
             }
         }
     }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 775570c..dfa9dce 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,7 +37,6 @@
         "com_android_server_adb_AdbDebuggingManager.cpp",
         "com_android_server_am_BatteryStatsService.cpp",
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
-        "com_android_server_BootReceiver.cpp",
         "com_android_server_ConsumerIrService.cpp",
         "com_android_server_companion_virtual_InputController.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -95,16 +94,6 @@
     header_libs: [
         "bionic_libc_platform_headers",
     ],
-
-    static_libs: [
-        "libunwindstack",
-    ],
-
-    whole_static_libs: [
-        "libdebuggerd_tombstone_proto_to_text",
-    ],
-
-    runtime_libs: ["libdexfile"],
 }
 
 cc_defaults {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 15eb7c6..cc08488 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -33,7 +33,3 @@
 
 # Bug component : 158088 = per-file *AnrTimer*
 per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
-
-# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
-per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
-per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS
diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp
deleted file mode 100644
index 3892d28..0000000
--- a/services/core/jni/com_android_server_BootReceiver.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <libdebuggerd/tombstone.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <sstream>
-
-#include "jni.h"
-#include "tombstone.pb.h"
-
-namespace android {
-
-static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) {
-    ss << line << std::endl;
-}
-
-static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject,
-                                                                jbyteArray tombstoneBytes) {
-    Tombstone tombstone;
-    tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0),
-                             env->GetArrayLength(tombstoneBytes));
-
-    std::stringstream tombstoneString;
-
-    tombstone_proto_to_text(tombstone,
-                            std::bind(&writeToString, std::ref(tombstoneString),
-                                      std::placeholders::_1, std::placeholders::_2));
-
-    return env->NewStringUTF(tombstoneString.str().c_str());
-}
-
-static const JNINativeMethod sMethods[] = {
-        /* name, signature, funcPtr */
-        {"getTombstoneText", "([B)Ljava/lang/String;",
-         (jstring*)com_android_server_BootReceiver_getTombstoneText},
-};
-
-int register_com_android_server_BootReceiver(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods,
-                                    NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 414339d..2b9bb7a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -685,6 +685,8 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
 
+        outConfig->mousePointerSpeed = mLocked.pointerSpeed;
+        outConfig->mousePointerAccelerationEnabled = mLocked.mousePointerAccelerationEnabled;
         outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
                 * POINTER_SPEED_EXPONENT);
         outConfig->pointerVelocityControlParameters.acceleration =
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index a4b1f84..5d1eb49 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -66,7 +66,6 @@
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
 int register_android_server_companion_virtual_InputController(JNIEnv* env);
 int register_android_server_app_GameManagerService(JNIEnv* env);
-int register_com_android_server_BootReceiver(JNIEnv* env);
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
 int register_com_android_server_SystemClockTime(JNIEnv* env);
@@ -129,7 +128,6 @@
     register_android_server_sensor_SensorService(vm, env);
     register_android_server_companion_virtual_InputController(env);
     register_android_server_app_GameManagerService(env);
-    register_com_android_server_BootReceiver(env);
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
     register_com_android_server_SystemClockTime(env);
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
index 94caf28..c8a6545 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -17,6 +17,7 @@
 package com.android.server.permission.access.appop
 
 import android.app.AppOpsManager
+import android.util.Slog
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.MutateStateScope
@@ -84,6 +85,10 @@
         appOpName: String,
         mode: Int
     ): Boolean {
+        if (userId !in newState.userStates) {
+            Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId")
+            return false
+        }
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
         val oldMode =
             newState.userStates[userId]!!
@@ -152,4 +157,8 @@
          */
         abstract fun onStateMutated()
     }
+
+    companion object {
+        private val LOG_TAG = AppIdAppOpPolicy::class.java.simpleName
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 0d9470e..2f15dc7 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -17,6 +17,7 @@
 package com.android.server.permission.access.appop
 
 import android.app.AppOpsManager
+import android.util.Slog
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.MutateStateScope
@@ -87,6 +88,10 @@
         appOpName: String,
         mode: Int
     ): Boolean {
+        if (userId !in newState.userStates) {
+            Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId")
+            return false
+        }
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
         val oldMode =
             newState.userStates[userId]!!
@@ -155,4 +160,8 @@
          */
         abstract fun onStateMutated()
     }
+
+    companion object {
+        private val LOG_TAG = PackageAppOpPolicy::class.java.simpleName
+    }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 83479e2..dd8c6a2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -295,6 +295,7 @@
     private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
 
     private Context mContext;
+    private Resources mResources;
     private FakesInjector mInjector;
     private Handler mHandler;
     @Rule
@@ -318,6 +319,8 @@
     @Before
     public void setUp() throws Exception {
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        mResources = mockResources();
+        when(mContext.getResources()).thenReturn(mResources);
         final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
         when(mContext.getContentResolver()).thenReturn(resolver);
         mInjector = spy(new FakesInjector(mDisplayManagerInternalMock, mStatusBarMock,
@@ -325,6 +328,60 @@
         mHandler = new Handler(Looper.getMainLooper());
     }
 
+    private Resources mockResources() {
+        var resources = mock(Resources.class);
+        when(resources.getBoolean(R.bool.config_ignoreUdfpsVote))
+                .thenReturn(false);
+        when(resources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled))
+                .thenReturn(false);
+        when(resources.getBoolean(R.bool.config_supportsDvrr))
+                .thenReturn(false);
+        when(resources.getInteger(R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+                .thenReturn(10000);
+        when(resources.getInteger(R.integer.config_defaultPeakRefreshRate))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_externalDisplayPeakWidth))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_externalDisplayPeakHeight))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_defaultRefreshRate))
+                .thenReturn(60);
+        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
+                .thenReturn(0);
+
+        when(resources.getString(R.string.config_displayLightSensorType))
+                .thenReturn(null);
+
+        when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+                .thenReturn(new int[]{});
+        when(resources.getIntArray(
+                R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+                .thenReturn(new int[]{});
+        when(resources.getIntArray(
+                R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+                .thenReturn(new int[]{});
+        when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+                .thenReturn(new int[]{});
+
+        doAnswer(invocation -> {
+            TypedValue value = invocation.getArgument(1);
+            value.type = TypedValue.TYPE_FLOAT;
+            value.data = Float.floatToIntBits(10f);
+            return null; // void method, so return null
+        }).when(resources).getValue(eq(R.dimen.config_displayWhiteBalanceBrightnessFilterIntercept),
+                any(), eq(true));
+
+        return resources;
+    }
+
     private DisplayModeDirector createDirectorFromRefreshRateArray(
             float[] refreshRates, int baseModeId) {
         return createDirectorFromRefreshRateArray(refreshRates, baseModeId, refreshRates[0]);
@@ -2911,31 +2968,31 @@
 
     @Test
     public void testNotifyDefaultDisplayDeviceUpdated() {
-        Resources resources = mock(Resources.class);
-        when(mContext.getResources()).thenReturn(resources);
-        when(resources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+        when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
             .thenReturn(75);
-        when(resources.getInteger(R.integer.config_defaultRefreshRate))
+        when(mResources.getInteger(R.integer.config_defaultRefreshRate))
             .thenReturn(45);
-        when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+        when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
             .thenReturn(65);
-        when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+        when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
             .thenReturn(85);
-        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
+        when(mResources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
             .thenReturn(95);
-        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
+        when(mResources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
             .thenReturn(100);
-        when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+        when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
             .thenReturn(new int[]{5});
-        when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+        when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
             .thenReturn(new int[]{10});
         when(
-            resources.getIntArray(R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+            mResources.getIntArray(
+                    R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
             .thenReturn(new int[]{250});
         when(
-            resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+            mResources.getIntArray(
+                    R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
             .thenReturn(new int[]{7000});
-        when(resources.getInteger(
+        when(mResources.getInteger(
             com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
             .thenReturn(3);
         ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
@@ -2943,7 +3000,7 @@
             valueArgumentCaptor.getValue().type = 4;
             valueArgumentCaptor.getValue().data = 13;
             return null;
-        }).when(resources).getValue(eq(com.android.internal.R.dimen
+        }).when(mResources).getValue(eq(com.android.internal.R.dimen
                 .config_displayWhiteBalanceBrightnessFilterIntercept),
                 valueArgumentCaptor.capture(), eq(true));
         DisplayModeDirector director =
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
index b363fd4..d781433 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
@@ -20,11 +20,13 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.media.projection.MediaProjectionInfo;
@@ -35,6 +37,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper.RunWithLooper;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 
@@ -52,7 +55,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Collections;
 import java.util.Set;
 
 @SmallTest
@@ -68,6 +70,8 @@
     private static final int NOTIFICATION_UID_1 = 5;
     private static final int NOTIFICATION_UID_2 = 6;
 
+    private static final ArraySet<PackageInfo> EMPTY_SET = new ArraySet<>();
+
     @Rule
     public final TestableContext mContext =
             new TestableContext(getInstrumentation().getTargetContext(), null);
@@ -107,6 +111,9 @@
 
         mSensitiveContentProtectionManagerService.mNotificationListener =
                 spy(mSensitiveContentProtectionManagerService.mNotificationListener);
+        doCallRealMethod()
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .onListenerConnected();
 
         // Setup RankingMap and two possilbe rankings
         when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true);
@@ -128,7 +135,7 @@
         mSensitiveContentProtectionManagerService.onDestroy();
     }
 
-    private Set<PackageInfo> setupSensitiveNotification() {
+    private ArraySet<PackageInfo> setupSensitiveNotification() {
         // Setup Notification Values
         when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
         when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -149,10 +156,11 @@
         when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
                 .thenReturn(mNonSensitiveRanking);
 
-        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+        return new ArraySet<>(
+                Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
     }
 
-    private Set<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
+    private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
         // Setup Notification Values
         when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
         when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -173,10 +181,11 @@
         when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
                 .thenReturn(mSensitiveRanking);
 
-        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+        return new ArraySet<>(
+                Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
     }
 
-    private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
+    private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
         // Setup Notification Values
         when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
         when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -197,11 +206,12 @@
         when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
                 .thenReturn(mSensitiveRanking);
 
-        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
-                new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1));
+        return new ArraySet<>(
+                Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+                        new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1)));
     }
 
-    private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
+    private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
         // Setup Notification Values
         when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
         when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -222,8 +232,9 @@
         when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
                 .thenReturn(mSensitiveRanking);
 
-        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
-                new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2));
+        return new ArraySet<>(
+                Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+                        new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2)));
     }
 
     private void setupNoSensitiveNotifications() {
@@ -251,11 +262,11 @@
 
     @Test
     public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() {
-        Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+        ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
     }
 
     @Test
@@ -264,7 +275,7 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
     }
 
     @Test
@@ -273,37 +284,37 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
     }
 
     @Test
     public void mediaProjectionOnStart_multipleNotifications_setWmBlockedPackages() {
-        Set<PackageInfo> expectedBlockedPackages =
+        ArraySet<PackageInfo> expectedBlockedPackages =
                 setupMultipleSensitiveNotificationsFromSamePackageAndUid();
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
     }
 
     @Test
     public void mediaProjectionOnStart_multiplePackages_setWmBlockedPackages() {
-        Set<PackageInfo> expectedBlockedPackages =
+        ArraySet<PackageInfo> expectedBlockedPackages =
                 setupMultipleSensitiveNotificationsFromDifferentPackage();
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
     }
 
     @Test
     public void mediaProjectionOnStart_multipleUid_setWmBlockedPackages() {
-        Set<PackageInfo> expectedBlockedPackages =
+        ArraySet<PackageInfo> expectedBlockedPackages =
                 setupMultipleSensitiveNotificationsFromDifferentUid();
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
     }
 
     @Test
@@ -316,12 +327,12 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).clearBlockedApps();
     }
 
     @Test
     public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() {
-        Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+        ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
 
         MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
         mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
@@ -330,7 +341,7 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
     }
 
     @Test
@@ -341,7 +352,7 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
     }
 
     @Test
@@ -352,7 +363,7 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
     }
 
     @Test
@@ -363,7 +374,7 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
     }
 
     @Test
@@ -376,6 +387,314 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_projectionNotStarted_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_projectionStopped_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_projectionStarted_setWmBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_noSensitiveNotifications_noBlockedPackages() {
+        setupNoSensitiveNotifications();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_noNotifications_noBlockedPackages() {
+        setupNoNotifications();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_nullRankingMap_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+        doReturn(null)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getCurrentRanking();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_missingRanking_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+        doReturn(mRankingMap)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getCurrentRanking();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_projectionNotStarted_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_projectionStopped_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_projectionStarted_setWmBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_noSensitiveNotifications_noBlockedPackages() {
+        setupNoSensitiveNotifications();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_noNotifications_noBlockedPackages() {
+        setupNoNotifications();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_nullRankingMap_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(null);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_missingRanking_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+        doReturn(mRankingMap)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getCurrentRanking();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_getActiveNotificationsThrows_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        doThrow(SecurityException.class)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getActiveNotifications();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_projectionNotStarted_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_projectionStopped_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_projectionStarted_setWmBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, mRankingMap);
+
+        ArraySet<PackageInfo> expectedBlockedPackages = new ArraySet<>(
+                Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_noSensitiveNotifications_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification2, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_noNotifications_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(null, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_nullRankingMap_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, null);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_missingRanking_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
new file mode 100644
index 0000000..7d3a110
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.app.ApplicationStartInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.text.TextUtils;
+
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+/**
+ * Test class for {@link android.app.ApplicationStartInfo}.
+ *
+ * Build/Install/Run:
+ * atest ApplicationStartInfoTest
+ */
+@Presubmit
+public class ApplicationStartInfoTest {
+
+    private static final String TAG = ApplicationStartInfoTest.class.getSimpleName();
+    private static final ComponentName COMPONENT = new ComponentName("com.android.test", ".Foo");
+
+    @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+    @Mock private AppOpsService mAppOpsService;
+    @Mock private PackageManagerInternal mPackageManagerInt;
+
+    private Context mContext = getInstrumentation().getTargetContext();
+    private TestInjector mInjector;
+    private ActivityManagerService mAms;
+    private ProcessList mProcessList;
+    private AppStartInfoTracker mAppStartInfoTracker;
+    private Handler mHandler;
+    private HandlerThread mHandlerThread;
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mProcessList = spy(new ProcessList());
+        mAppStartInfoTracker = spy(new AppStartInfoTracker());
+        mAppStartInfoTracker.mEnabled = true;
+        setFieldValue(ProcessList.class, mProcessList, "mAppStartInfoTracker",
+                mAppStartInfoTracker);
+        mInjector = new TestInjector(mContext);
+        mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
+        mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+        mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+        mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
+        mAms.mPackageManagerInt = mPackageManagerInt;
+        mAppStartInfoTracker.mService = mAms;
+        doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+        doReturn("com.android.test").when(mPackageManagerInt).getNameForUid(anyInt());
+        // Remove stale instance of PackageManagerInternal if there is any
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+    }
+
+    @After
+    public void tearDown() {
+        mHandlerThread.quit();
+    }
+
+    @Test
+    public void testApplicationStartInfo() throws Exception {
+        mAppStartInfoTracker.clearProcessStartInfo(true);
+        mAppStartInfoTracker.mAppStartInfoLoaded.set(true);
+        mAppStartInfoTracker.mAppStartInfoHistoryListSize =
+                mAppStartInfoTracker.APP_START_INFO_HISTORY_LIST_SIZE;
+        mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
+                AppStartInfoTracker.APP_START_STORE_DIR);
+        assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
+        mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
+                AppStartInfoTracker.APP_START_INFO_FILE);
+
+        doNothing().when(mAppStartInfoTracker).schedulePersistProcessStartInfo(anyBoolean());
+
+        final int app1Uid = 10123;
+        final int app1Pid1 = 12345;
+        final int app1Pid2 = 12346;
+        final int app1DefiningUid = 23456;
+        final int app1UidUser2 = 1010123;
+        final int app1PidUser2 = 12347;
+        final String app1ProcessName = "com.android.test.stub1:process";
+        final String app1PackageName = "com.android.test.stub1";
+        final long appStartTimestampIntentStarted = 1000000;
+        final long appStartTimestampActivityLaunchFinished = 2000000;
+        final long appStartTimestampReportFullyDrawn = 3000000;
+        final long appStartTimestampService = 4000000;
+        final long appStartTimestampBroadcast = 5000000;
+        final long appStartTimestampRContentProvider = 6000000;
+
+        ProcessRecord app = makeProcessRecord(
+                app1Pid1,                    // pid
+                app1Uid,                     // uid
+                app1Uid,                     // packageUid
+                null,                        // definingUid
+                app1ProcessName,             // processName
+                app1PackageName);            // packageName
+
+        ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
+
+        // Case 1: Activity start intent failed
+        mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+                appStartTimestampIntentStarted);
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 0);
+
+        verifyInProgApplicationStartInfo(
+                0,                                                    // index
+                0,                                                    // pid
+                0,                                                    // uid
+                0,                                                    // packageUid
+                null,                                                 // definingUid
+                null,                                                 // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_UNSET,                // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        mAppStartInfoTracker.onIntentFailed(appStartTimestampIntentStarted);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(0);
+        assertEquals(list.size(), 0);
+
+        mAppStartInfoTracker.clearProcessStartInfo(true);
+
+        // Case 2: Activity start launch cancelled
+        mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+                appStartTimestampIntentStarted);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 0);
+
+        mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT,
+                ApplicationStartInfo.START_TYPE_COLD, app);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 1);
+
+        verifyInProgApplicationStartInfo(
+                0,                                                    // index
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        mAppStartInfoTracker.onActivityLaunchCancelled(appStartTimestampIntentStarted);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(0);
+        assertEquals(list.size(), 1);
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_ERROR,             // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        mAppStartInfoTracker.clearProcessStartInfo(true);
+
+        // Case 3: Activity start success
+        mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+                appStartTimestampIntentStarted);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 0);
+
+        mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT,
+                ApplicationStartInfo.START_TYPE_COLD, app);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 1);
+
+        verifyInProgApplicationStartInfo(
+                0,                                                    // index
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        mAppStartInfoTracker.onActivityLaunchFinished(appStartTimestampIntentStarted, COMPONENT,
+                appStartTimestampActivityLaunchFinished, ApplicationStartInfo.LAUNCH_MODE_STANDARD);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 1);
+
+        verifyInProgApplicationStartInfo(
+                0,                                                    // index
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        mAppStartInfoTracker.onReportFullyDrawn(appStartTimestampIntentStarted,
+                appStartTimestampReportFullyDrawn);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(0);
+        assertEquals(list.size(), 1);
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        // Don't clear records for use in subsequent cases.
+
+        // Case 4: Create an other app1 record with different pid started for a service
+        sleep(1);
+        app = makeProcessRecord(
+                app1Pid2,                    // pid
+                app1Uid,                     // uid
+                app1Uid,                     // packageUid
+                app1DefiningUid,             // definingUid
+                app1ProcessName,             // processName
+                app1PackageName);            // packageName
+        ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms);
+
+        mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service,
+                false);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list);
+        assertEquals(list.size(), 2);
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app1Pid2,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                app1DefiningUid,                                      // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_SERVICE,            // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_WARM,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        // Case 5: Create an instance of app1 with a different user started for a broadcast
+        sleep(1);
+        app = makeProcessRecord(
+                app1PidUser2,                    // pid
+                app1UidUser2,                    // uid
+                app1UidUser2,                    // packageUid
+                null,                            // definingUid
+                app1ProcessName,                 // processName
+                app1PackageName);                // packageName
+
+        mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app,
+                null, true /* isColdStart */);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+        assertEquals(list.size(), 1);
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app1PidUser2,                                         // pid
+                app1UidUser2,                                         // uid
+                app1UidUser2,                                         // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_BROADCAST,          // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        // Case 6: User 2 gets removed
+        mAppStartInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+        assertEquals(list.size(), 0);
+
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1PidUser2, 0, list);
+        assertEquals(list.size(), 2);
+
+
+        // Case 7: Create a process from another package started for a content provider
+        final int app2UidUser2 = 1010234;
+        final int app2PidUser2 = 12348;
+        final String app2ProcessName = "com.android.test.stub2:process";
+        final String app2PackageName = "com.android.test.stub2";
+
+        sleep(1);
+
+        app = makeProcessRecord(
+                app2PidUser2,                    // pid
+                app2UidUser2,                    // uid
+                app2UidUser2,                    // packageUid
+                null,                            // definingUid
+                app2ProcessName,                 // processName
+                app2PackageName);                // packageName
+
+        mAppStartInfoTracker.handleProcessContentProviderStart(appStartTimestampRContentProvider,
+                app, false);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
+        assertEquals(list.size(), 1);
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app2PidUser2,                                         // pid
+                app2UidUser2,                                         // uid
+                app2UidUser2,                                         // packageUid
+                null,                                                 // definingUid
+                app2ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_CONTENT_PROVIDER,   // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_WARM,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        // Case 8: Save and load again
+        ArrayList<ApplicationStartInfo> original = new ArrayList<ApplicationStartInfo>();
+        mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, original);
+        assertTrue(original.size() > 0);
+
+        mAppStartInfoTracker.persistProcessStartInfo();
+        assertTrue(mAppStartInfoTracker.mProcStartInfoFile.exists());
+
+        mAppStartInfoTracker.clearProcessStartInfo(false);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list);
+        assertEquals(0, list.size());
+
+        mAppStartInfoTracker.loadExistingProcessStartInfo();
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list);
+        assertEquals(original.size(), list.size());
+
+        for (int i = list.size() - 1; i >= 0; i--) {
+            assertTrue(list.get(i).equals(original.get(i)));
+        }
+    }
+
+    private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+        try {
+            Field field = clazz.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            Field mfield = Field.class.getDeclaredField("accessFlags");
+            mfield.setAccessible(true);
+            mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+            field.set(obj, val);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+        }
+    }
+
+    private void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+        }
+    }
+
+    private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+            String processName, String packageName) {
+        return makeProcessRecord(pid, uid, packageUid, definingUid, processName, packageName, mAms);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    static ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+            String processName, String packageName, ActivityManagerService ams) {
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ProcessRecord app = new ProcessRecord(ams, ai, processName, uid);
+        app.setPid(pid);
+        app.info.uid = packageUid;
+        if (definingUid != null) {
+            app.setHostingRecord(HostingRecord.byAppZygote(COMPONENT, "", definingUid, ""));
+        }
+        return app;
+    }
+
+    private static Intent buildIntent(ComponentName componentName) throws Exception {
+        Intent intent = new Intent();
+        intent.setComponent(componentName);
+        intent.setPackage(componentName.getPackageName());
+        return intent;
+    }
+
+    private void verifyInProgressRecordsSize(int expectedSize) {
+        synchronized (mAppStartInfoTracker.mLock) {
+            assertEquals(mAppStartInfoTracker.mInProgRecords.size(), expectedSize);
+        }
+    }
+
+    private void verifyInProgApplicationStartInfo(int index,
+            Integer pid, Integer uid, Integer packageUid,
+            Integer definingUid, String processName,
+            Integer reason, Integer startupState, Integer startType, Integer launchMode) {
+        synchronized (mAppStartInfoTracker.mLock) {
+            verifyApplicationStartInfo(mAppStartInfoTracker.mInProgRecords.valueAt(index),
+                    pid, uid, packageUid, definingUid, processName, reason, startupState,
+                    startType, launchMode);
+        }
+    }
+
+    private void verifyApplicationStartInfo(ApplicationStartInfo info,
+            Integer pid, Integer uid, Integer packageUid,
+            Integer definingUid, String processName,
+            Integer reason, Integer startupState, Integer startType, Integer launchMode) {
+        assertNotNull(info);
+
+        if (pid != null) {
+            assertEquals(pid.intValue(), info.getPid());
+        }
+        if (uid != null) {
+            assertEquals(uid.intValue(), info.getRealUid());
+        }
+        if (packageUid != null) {
+            assertEquals(packageUid.intValue(), info.getPackageUid());
+        }
+        if (definingUid != null) {
+            assertEquals(definingUid.intValue(), info.getDefiningUid());
+        }
+        if (processName != null) {
+            assertTrue(TextUtils.equals(processName, info.getProcessName()));
+        }
+        if (reason != null) {
+            assertEquals(reason.intValue(), info.getReason());
+        }
+        if (startupState != null) {
+            assertEquals(startupState.intValue(), info.getStartupState());
+        }
+        if (startType != null) {
+            assertEquals(startType.intValue(), info.getStartType());
+        }
+        if (launchMode != null) {
+            assertEquals(launchMode.intValue(), info.getLaunchMode());
+        }
+    }
+
+    private class TestInjector extends Injector {
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandler;
+        }
+
+        @Override
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return mProcessList;
+        }
+    }
+
+    static class ServiceThreadRule implements TestRule {
+
+        private ServiceThread mThread;
+
+        ServiceThread getThread() {
+            return mThread;
+        }
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    mThread = new ServiceThread("TestServiceThread",
+                            Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
+                    mThread.start();
+                    try {
+                        base.evaluate();
+                    } finally {
+                        mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */);
+                    }
+                }
+            };
+        }
+    }
+
+    // TODO: [b/302724778] Remove manual JNI load
+    static {
+        System.loadLibrary("mockingservicestestjni");
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 116d5db..284e491 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -426,6 +426,8 @@
 
     @Test
     public void testGetNextConstraintDropTimeElapsedLocked() {
+        setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
+
         long nextTimeToDropNumConstraints;
 
         // no delay, deadline
@@ -457,15 +459,18 @@
 
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(130400100, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) / 2,
+                nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(156320100L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 6 / 10,
+                nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(182240100L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 7 / 10,
+                nextTimeToDropNumConstraints);
 
         // no delay, no deadline
         jb = createJob(0);
@@ -473,15 +478,15 @@
 
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(129600100, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(155520100L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(181440100L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints);
 
         // delay, deadline
         jb = createJob(0)
@@ -950,8 +955,7 @@
         mJobStore.add(js);
 
         // Needed because if before and after Uid bias is the same, nothing happens.
-        when(mJobSchedulerService.getUidBias(mSourceUid))
-                .thenReturn(JobInfo.BIAS_DEFAULT);
+        doReturn(JobInfo.BIAS_DEFAULT).when(mJobSchedulerService).getUidBias(mSourceUid);
 
         synchronized (mFlexibilityController.mLock) {
             mFlexibilityController.maybeStartTrackingJobLocked(js, null);
@@ -1328,7 +1332,7 @@
 
         final ArraySet<String> pkgs = new ArraySet<>();
         pkgs.add(js.getSourcePackageName());
-        when(mJobSchedulerService.getPackagesForUidLocked(mSourceUid)).thenReturn(pkgs);
+        doReturn(pkgs).when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid);
 
         setUidBias(mSourceUid, BIAS_TOP_APP);
         setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 0403c64..ec7e359 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -233,7 +233,7 @@
         Exception e = assertThrows(
                 SecurityException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, "different", mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e).hasMessageThat().isEqualTo(
                 String.format(
                         "The UID %s of callerPackageName set by the caller doesn't match the "
@@ -250,7 +250,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s not found.", PACKAGE));
@@ -260,8 +260,7 @@
     public void archiveApp_packageNotInstalledForUser() throws IntentSender.SendIntentException {
         mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
 
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
-                0);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -291,7 +290,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo("No installer found");
     }
@@ -305,7 +304,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 "Installer does not support unarchival");
@@ -319,7 +318,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE));
@@ -331,8 +330,7 @@
         doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
                 any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
 
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
-                0);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -355,7 +353,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 TextUtils.formatSimple("The app %s is opted out of archiving.", PACKAGE));
@@ -363,8 +361,7 @@
 
     @Test
     public void archiveApp_withNoAdditionalFlags_success() {
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
-                0);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         verify(mInstallerService).uninstall(
@@ -386,14 +383,13 @@
 
     @Test
     public void archiveApp_withAdditionalFlags_success() {
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
-                PackageManager.DELETE_SHOW_DIALOG);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         verify(mInstallerService).uninstall(
                 eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
                 eq(CALLER_PACKAGE),
-                eq(DELETE_ARCHIVE | DELETE_KEEP_DATA | PackageManager.DELETE_SHOW_DIALOG),
+                eq(DELETE_ARCHIVE | DELETE_KEEP_DATA),
                 eq(mIntentSender),
                 eq(UserHandle.CURRENT.getIdentifier()), anyInt());
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index fd6aa0c..e6298ee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -19,9 +19,11 @@
 import static android.os.UserManager.DISALLOW_SMS;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -30,20 +32,27 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.multiuser.Flags;
+import android.os.PowerManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Pair;
@@ -52,6 +61,7 @@
 
 import androidx.test.annotation.UiThreadTest;
 
+import com.android.dx.mockito.inline.extended.MockedVoidMethod;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.testing.ExtendedMockitoRule;
@@ -65,6 +75,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -115,8 +126,11 @@
             .spyStatic(LocalServices.class)
             .spyStatic(SystemProperties.class)
             .mockStatic(Settings.Global.class)
+            .mockStatic(Settings.Secure.class)
             .build();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private final Object mPackagesLock = new Object();
     private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
             .getTargetContext();
@@ -133,6 +147,8 @@
     private @Mock StorageManager mStorageManager;
     private @Mock LockSettingsInternal mLockSettingsInternal;
     private @Mock PackageManagerInternal mPackageManagerInternal;
+    private @Mock KeyguardManager mKeyguardManager;
+    private @Mock PowerManager mPowerManager;
 
     /**
      * Reference to the {@link UserManagerService} being tested.
@@ -156,6 +172,8 @@
         when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
         mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
         when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
+        when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+        when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
         mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
         mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
         doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
@@ -550,6 +568,143 @@
         assertTrue(hasRestrictionsInUserXMLFile(user.id));
     }
 
+    @Test
+    public void testAutoLockOnDeviceLockForPrivateProfile() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+        UserManagerService mSpiedUms = spy(mUms);
+        UserInfo privateProfileUser =
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+        mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+        Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
+                any());
+
+        mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
+
+        Mockito.verify(mSpiedUms).setQuietModeEnabledAsync(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+                        any(), any());
+    }
+
+    @Test
+    public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+        UserManagerService mSpiedUms = spy(mUms);
+        UserInfo privateProfileUser =
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+        mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+
+        mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(false);
+
+        // Verify that no operation to disable quiet mode is not called
+        Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+                any(), any());
+    }
+
+    @Test
+    public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+        UserManagerService mSpiedUms = spy(mUms);
+        UserInfo privateProfileUser =
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+
+        mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
+
+        // Verify that no auto-lock operations take place
+        verify((MockedVoidMethod) () -> Settings.Secure.getInt(any(),
+                eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), anyInt()), never());
+        Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+                any(), any());
+    }
+
+    @Test
+    public void testAutoLockAfterInactityForPrivateProfile() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+        UserManagerService mSpiedUms = spy(mUms);
+        mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+        when(mPowerManager.isInteractive()).thenReturn(false);
+
+        UserInfo privateProfileUser =
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                        USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+        Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
+                anyLong());
+
+
+        mSpiedUms.maybeScheduleMessageToAutoLockPrivateSpace();
+
+        Mockito.verify(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), any(), anyLong());
+    }
+
+    @Test
+    public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+
+        mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+
+        Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+        Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+        Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener((any()));
+        Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+    }
+
+    @Test
+    public void testSetOrUpdateAutoLockPreference() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+        mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                        USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+
+        // Set the preference to auto lock on device lock
+        mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+
+        // Verify that keyguard state listener was added
+        Mockito.verify(mKeyguardManager).addKeyguardLockedStateListener(any(), any());
+        //Verity that keyguard state listener was not removed
+        Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener(any());
+        // Broadcasts are already unregistered when UserManagerService starts and the flag
+        // isDeviceInactivityBroadcastReceiverRegistered is false
+        Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+        Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+
+        Mockito.clearInvocations(mKeyguardManager);
+        Mockito.clearInvocations(mSpiedContext);
+
+        // Now set the preference to auto-lock on inactivity
+        mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+
+        // Verify that inactivity broadcasts are registered
+        Mockito.verify(mSpiedContext, times(2)).registerReceiver(any(), any(), any(), any());
+        // Verify that keyguard state listener is removed
+        Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any());
+        // Verify that all other operations don't take place
+        Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+        Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+
+        Mockito.clearInvocations(mKeyguardManager);
+        Mockito.clearInvocations(mSpiedContext);
+
+        // Finally, set the preference to don't auto-lock
+        mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER);
+
+        // Verify that inactivity broadcasts are unregistered and keyguard listener was removed
+        Mockito.verify(mSpiedContext).unregisterReceiver(any());
+        Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any());
+        // Verify that no broadcasts were registered and no listeners were added
+        Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+        Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+    }
+
     /**
      * Returns true if the user's XML file has Default restrictions
      * @param userId Id of the user.
@@ -632,6 +787,12 @@
                 SystemProperties.getBoolean(eq("fw.show_multiuserui"), anyBoolean()));
     }
 
+    private void mockAutoLockForPrivateSpace(int val) {
+        doReturn(val).when(() ->
+                Settings.Secure.getIntForUser(any(), eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK),
+                        anyInt(), anyInt()));
+    }
+
     private void mockCurrentUser(@UserIdInt int userId) {
         mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 37ca09d..b415682 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -47,6 +47,10 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -69,6 +73,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
 import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -105,6 +110,8 @@
 
     private static final String URI_SCHEME_PACKAGE = "package";
     private static final int TEST_USER_ID = 50;
+    private static final UserInfo TEST_USER =
+            new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL);
     private static final int PARENT_USER_ID = 60;
     private static final int PROFILE_USER_ID = 70;
     private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L };
@@ -117,6 +124,8 @@
     private @Mock ActivityManager mActivityManager;
     private @Mock BiometricManager mBiometricManager;
     private @Mock DevicePolicyManager mDevicePolicyManager;
+    private @Mock FaceManager mFaceManager;
+    private @Mock FingerprintManager mFingerprintManager;
     private @Mock IKeystoreAuthorization mKeystoreAuthorization;
     private @Mock LockPatternUtils mLockPatternUtils;
     private @Mock PackageManager mPackageManager;
@@ -133,6 +142,9 @@
         when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
         doReturn(mock(IActivityManager.class)).when(() -> ActivityManager.getService());
 
+        when(mFaceManager.getSensorProperties()).thenReturn(List.of());
+        when(mFingerprintManager.getSensorProperties()).thenReturn(List.of());
+
         doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
 
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
@@ -161,13 +173,16 @@
         when(mPackageManager.checkPermission(any(), any())).thenReturn(
                 PackageManager.PERMISSION_GRANTED);
 
-        when(mUserManager.getAliveUsers()).thenReturn(
-                List.of(new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL)));
+        when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER));
+        when(mUserManager.getEnabledProfileIds(TEST_USER_ID)).thenReturn(new int[0]);
+        when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(TEST_USER);
 
         when(mWindowManager.isKeyguardLocked()).thenReturn(true);
 
         mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
         mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager);
+        mMockContext.addMockSystemService(FaceManager.class, mFaceManager);
+        mMockContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
         mMockContext.setMockPackageManager(mPackageManager);
         mMockContext.addMockSystemService(UserManager.class, mUserManager);
         doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
@@ -362,9 +377,9 @@
         when(mWindowManager.isKeyguardLocked()).thenReturn(true);
         mTrustManager.reportKeyguardShowingChanged();
         verify(mKeystoreAuthorization)
-                .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+                .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
         verify(mKeystoreAuthorization)
-                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
     }
 
     // Tests that when the device is locked for a managed profile with a *separate* challenge, the
@@ -381,7 +396,188 @@
 
         mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
         verify(mKeystoreAuthorization)
-                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS));
+                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS), eq(false));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenWeakFingerprintIsSetupAndAllowed()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenWeakFaceIsSetupAndAllowed() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenConvenienceFingerprintIsSetupAndAllowed()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_CONVENIENCE);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenConvenienceFaceIsSetupAndAllowed()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_CONVENIENCE);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenStrongAuthRequired() throws Exception {
+        setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, true);
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenNonStrongBiometricNotAllowed() throws Exception {
+        setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED,
+                /* isNonStrongBiometricAllowed= */ false);
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenWeakFingerprintSensorIsPresentButNotEnrolled()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenWeakFaceSensorIsPresentButNotEnrolled()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void
+            testKeystoreWeakUnlockDisabled_whenWeakFingerprintIsSetupButForbiddenByDevicePolicy()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_WEAK);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenWeakFaceIsSetupButForbiddenByDevicePolicy()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFingerprintIsSetup() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_STRONG);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFaceIsSetup() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_STRONG);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenNoBiometricsAreSetup() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        verifyWeakUnlockDisabled();
+    }
+
+    private void setupStrongAuthTrackerToAllowEverything() throws Exception {
+        setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED, true);
+    }
+
+    private void setupStrongAuthTracker(int strongAuthFlags, boolean isNonStrongBiometricAllowed)
+            throws Exception {
+        bootService();
+        mService.onUserSwitching(null, new SystemService.TargetUser(TEST_USER));
+
+        ArgumentCaptor<StrongAuthTracker> strongAuthTracker =
+                ArgumentCaptor.forClass(StrongAuthTracker.class);
+        verify(mLockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture());
+        strongAuthTracker.getValue().getStub().onStrongAuthRequiredChanged(
+                strongAuthFlags, TEST_USER_ID);
+        strongAuthTracker.getValue().getStub().onIsNonStrongBiometricAllowedChanged(
+                isNonStrongBiometricAllowed, TEST_USER_ID);
+        mService.waitForIdle();
+    }
+
+    private void setupFingerprint(int strength) {
+        setupFingerprint(strength, /* enrolled= */ true);
+    }
+
+    private void setupFingerprint(int strength, boolean enrolled) {
+        int sensorId = 100;
+        List<SensorProperties.ComponentInfo> componentInfo = List.of();
+        SensorProperties sensor = new SensorProperties(sensorId, strength, componentInfo);
+        when(mFingerprintManager.getSensorProperties()).thenReturn(List.of(sensor));
+        when(mFingerprintManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
+    }
+
+    private void setupFace(int strength) {
+        setupFace(strength, /* enrolled= */ true);
+    }
+
+    private void setupFace(int strength, boolean enrolled) {
+        int sensorId = 100;
+        List<SensorProperties.ComponentInfo> componentInfo = List.of();
+        FaceSensorProperties sensor = new FaceSensorProperties(
+                sensorId, strength, componentInfo, FaceSensorProperties.TYPE_RGB);
+        when(mFaceManager.getSensorProperties()).thenReturn(List.of(sensor));
+        when(mFaceManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
+    }
+
+    private void verifyWeakUnlockEnabled() throws Exception {
+        verifyWeakUnlockValue(true);
+    }
+
+    private void verifyWeakUnlockDisabled() throws Exception {
+        verifyWeakUnlockValue(false);
+    }
+
+    // Simulates a device unlock and a device lock, then verifies that the expected
+    // weakUnlockEnabled flag was passed to Keystore's onDeviceLocked method.
+    private void verifyWeakUnlockValue(boolean expectedWeakUnlockEnabled) throws Exception {
+        when(mWindowManager.isKeyguardLocked()).thenReturn(false);
+        mTrustManager.reportKeyguardShowingChanged();
+        verify(mKeystoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
+
+        when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+        mTrustManager.reportKeyguardShowingChanged();
+        verify(mKeystoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
+                eq(expectedWeakUnlockEnabled));
     }
 
     private void setupMocksForProfile(boolean unifiedChallenge) {
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 0045026..0831086 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -29,8 +29,6 @@
         "src/**/*.java",
         "src/**/*.kt",
 
-        "test-apps/JobTestApp/src/**/*.java",
-
         "test-apps/SuspendTestApp/src/**/*.java",
     ],
     static_libs: [
@@ -124,7 +122,6 @@
     },
 
     data: [
-        ":JobTestApp",
         ":SimpleServiceTestApp1",
         ":SimpleServiceTestApp2",
         ":SimpleServiceTestApp3",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index b1d5039..27c522d 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -29,7 +29,6 @@
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
         <option name="test-file-name" value="FrameworksServicesTests.apk" />
-        <option name="test-file-name" value="JobTestApp.apk" />
         <option name="test-file-name" value="SuspendTestApp.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp1.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp2.apk" />
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
deleted file mode 100644
index 523c5c0..0000000
--- a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.test.AndroidTestCase;
-
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
-
-public class BootReceiverTest extends AndroidTestCase {
-    private static final String TAG = "BootReceiverTest";
-
-    public void testRemoveMemoryFromTombstone() {
-        Tombstone tombstoneBase = Tombstone.newBuilder()
-                .setBuildFingerprint("build_fingerprint")
-                .setRevision("revision")
-                .setPid(123)
-                .setTid(23)
-                .setUid(34)
-                .setSelinuxLabel("selinux_label")
-                .addCommandLine("cmd1")
-                .addCommandLine("cmd2")
-                .addCommandLine("cmd3")
-                .setProcessUptime(300)
-                .setAbortMessage("abort")
-                .addCauses(TombstoneProtos.Cause.newBuilder()
-                        .setHumanReadable("cause1")
-                        .setMemoryError(TombstoneProtos.MemoryError.newBuilder()
-                                .setTool(TombstoneProtos.MemoryError.Tool.SCUDO)
-                                .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE)))
-                .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs(
-                        TombstoneProtos.LogMessage.newBuilder()
-                                .setTimestamp("123")
-                                .setMessage("message")))
-                .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path"))
-                .build();
-
-        Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder()
-                .putThreads(1, TombstoneProtos.Thread.newBuilder()
-                        .setId(1)
-                        .setName("thread1")
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
-                        .addBacktraceNote("backtracenote1")
-                        .addUnreadableElfFiles("files1")
-                        .setTaggedAddrCtrl(1)
-                        .setPacEnabledKeys(10)
-                        .build())
-                .build();
-
-        Tombstone tombstoneWithMemory = tombstoneBase.toBuilder()
-                .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder()
-                        .setBeginAddress(1)
-                        .setEndAddress(100)
-                        .setOffset(10)
-                        .setRead(true)
-                        .setWrite(true)
-                        .setExecute(false)
-                        .setMappingName("mapping")
-                        .setBuildId("build")
-                        .setLoadBias(70))
-                .putThreads(1, TombstoneProtos.Thread.newBuilder()
-                        .setId(1)
-                        .setName("thread1")
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
-                        .addBacktraceNote("backtracenote1")
-                        .addUnreadableElfFiles("files1")
-                        .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder()
-                                .setRegisterName("register1")
-                                .setMappingName("mapping")
-                                .setBeginAddress(10))
-                        .setTaggedAddrCtrl(1)
-                        .setPacEnabledKeys(10)
-                        .build())
-                .build();
-
-        assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory))
-                .isEqualTo(tombstoneWithoutMemory);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index d71844b..9ff29d2 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -829,6 +829,40 @@
     }
 
     @Test
+    public void getDisplayNameForPersistentDeviceId_nonExistentPeristentId_returnsNull() {
+        assertThat(mVdm.getDisplayNameForPersistentDeviceId("nonExistentPersistentId")).isNull();
+    }
+
+    @Test
+    public void getDisplayNameForPersistentDeviceId_defaultDevicePeristentId_returnsNull() {
+        assertThat(mVdm.getDisplayNameForPersistentDeviceId(
+                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT))
+                .isNull();
+    }
+
+    @Test
+    public void getDisplayNameForPersistentDeviceId_validVirtualDevice_returnsCorrectId() {
+        mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo));
+        CharSequence persistentIdDisplayName =
+                mVdm.getDisplayNameForPersistentDeviceId(mDeviceImpl.getPersistentDeviceId());
+        assertThat(persistentIdDisplayName.toString())
+                .isEqualTo(mAssociationInfo.getDisplayName().toString());
+    }
+
+    @Test
+    public void getDisplayNameForPersistentDeviceId_noVirtualDevice_returnsCorrectId() {
+        CharSequence displayName = "New display name for the new association";
+        mVdms.onCdmAssociationsChanged(List.of(
+                createAssociationInfo(2, AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+                        displayName)));
+
+        CharSequence persistentIdDisplayName =
+                mVdm.getDisplayNameForPersistentDeviceId(
+                        VirtualDeviceImpl.createPersistentDeviceId(2));
+        assertThat(persistentIdDisplayName.toString()).isEqualTo(displayName.toString());
+    }
+
+    @Test
     public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
@@ -1994,8 +2028,14 @@
     }
 
     private AssociationInfo createAssociationInfo(int associationId, String deviceProfile) {
+        return createAssociationInfo(
+                associationId, deviceProfile, /* displayName= */ deviceProfile);
+    }
+
+    private AssociationInfo createAssociationInfo(int associationId, String deviceProfile,
+            CharSequence displayName) {
         return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
-                /* tag= */ null, MacAddress.BROADCAST_ADDRESS, /* displayName= */ "", deviceProfile,
+                /* tag= */ null, MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
                 /* associatedDevice= */ null, /* selfManaged= */ true,
                 /* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
                 /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 543fa57..6207349 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -1775,6 +1775,11 @@
 
     @Test
     public void wakeUp_hotPlugIn_invokesDeviceDiscoveryOnce() {
+        // There might be a leftover HotplugDetectionAction that can interfere with the test.
+        mHdmiCecLocalDevicePlayback.removeAction(HotplugDetectionAction.class);
+
+        long pollingDelay = TimeUnit.SECONDS.toMillis(60);
+        mHdmiCecController.setPollDevicesDelay(pollingDelay);
         mNativeWrapper.setPollAddressResponse(Constants.ADDR_PLAYBACK_2, SendMessageResult.SUCCESS);
         mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
         mTestLooper.dispatchAll();
@@ -1783,6 +1788,14 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDevicePlayback.getActions(DeviceDiscoveryAction.class)).hasSize(1);
+        mTestLooper.moveTimeForward(pollingDelay);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage givePhysicalAddress = HdmiCecMessageBuilder.buildGivePhysicalAddress(
+                Constants.ADDR_PLAYBACK_1,
+                Constants.ADDR_PLAYBACK_2);
+        assertThat(mNativeWrapper.getResultMessages().stream()
+                .filter(message -> message.equals(givePhysicalAddress)).count()).isEqualTo(1);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
deleted file mode 100644
index e871fc5..0000000
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STARTED;
-import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
-import static com.android.servicestests.apps.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.app.IActivityManager;
-import android.app.job.JobParameters;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.IDeviceIdleController;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.servicestests.apps.jobtestapp.TestJobActivity;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests that background restrictions on jobs work as expected.
- * This test requires test-apps/JobTestApp to be installed on the device.
- * To run this test from root of checkout:
- * <pre>
- *  mmm -j32 frameworks/base/services/tests/servicestests/
- *  adb install -r $OUT/data/app/JobTestApp/JobTestApp.apk
- *  adb install -r $OUT/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
- *  adb  shell am instrument -e class 'com.android.server.job.BackgroundRestrictionsTest' -w \
- *  com.android.frameworks.servicestests
- * </pre>
- */
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class BackgroundRestrictionsTest {
-    private static final String TAG = BackgroundRestrictionsTest.class.getSimpleName();
-    private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp";
-    private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity";
-    private static final long POLL_INTERVAL = 500;
-    private static final long DEFAULT_WAIT_TIMEOUT = 10_000;
-
-    private Context mContext;
-    private AppOpsManager mAppOpsManager;
-    private IDeviceIdleController mDeviceIdleController;
-    private IActivityManager mIActivityManager;
-    private volatile int mTestJobId = -1;
-    private int mTestPackageUid;
-    /* accesses must be synchronized on itself */
-    private final TestJobStatus mTestJobStatus = new TestJobStatus();
-    private final BroadcastReceiver mJobStateChangeReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
-            Log.d(TAG, "Received action " + intent.getAction());
-            synchronized (mTestJobStatus) {
-                switch (intent.getAction()) {
-                    case ACTION_JOB_STARTED:
-                        mTestJobStatus.running = true;
-                        mTestJobStatus.jobId = params.getJobId();
-                        mTestJobStatus.stopReason = JobParameters.STOP_REASON_UNDEFINED;
-                        break;
-                    case ACTION_JOB_STOPPED:
-                        mTestJobStatus.running = false;
-                        mTestJobStatus.jobId = params.getJobId();
-                        mTestJobStatus.stopReason = params.getStopReason();
-                        break;
-                }
-            }
-        }
-    };
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
-                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-        mIActivityManager = ActivityManager.getService();
-        mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
-        mTestJobStatus.reset();
-        final IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ACTION_JOB_STARTED);
-        intentFilter.addAction(ACTION_JOB_STOPPED);
-        mContext.registerReceiver(mJobStateChangeReceiver, intentFilter,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
-        setAppOpsModeAllowed(true);
-        setPowerExemption(false);
-    }
-
-    private void scheduleTestJob() {
-        mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
-        final Intent scheduleJobIntent = new Intent(TestJobActivity.ACTION_START_JOB);
-        scheduleJobIntent.putExtra(TestJobActivity.EXTRA_JOB_ID_KEY, mTestJobId);
-        scheduleJobIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
-        mContext.startActivity(scheduleJobIntent);
-    }
-
-    private void scheduleAndAssertJobStarted() throws Exception {
-        scheduleTestJob();
-        Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
-        assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-    }
-
-    @FlakyTest
-    @Test
-    public void testPowerExemption() throws Exception {
-        scheduleAndAssertJobStarted();
-        setAppOpsModeAllowed(false);
-        mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT);
-        assertTrue("Job did not stop after putting app under bg-restriction",
-                awaitJobStop(DEFAULT_WAIT_TIMEOUT,
-                        JobParameters.STOP_REASON_BACKGROUND_RESTRICTION));
-
-        setPowerExemption(true);
-        scheduleTestJob();
-        Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
-        assertTrue("Job did not start when the app was in the power exemption list",
-                awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-
-        setPowerExemption(false);
-        assertTrue("Job did not stop after removing from the power exemption list",
-                awaitJobStop(DEFAULT_WAIT_TIMEOUT,
-                        JobParameters.STOP_REASON_BACKGROUND_RESTRICTION));
-
-        scheduleTestJob();
-        Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
-        assertFalse("Job started under bg-restrictions", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-        setPowerExemption(true);
-        assertTrue("Job did not start when the app was in the power exemption list",
-                awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS);
-        cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
-        cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(cancelJobsIntent);
-        mContext.unregisterReceiver(mJobStateChangeReceiver);
-        Thread.sleep(500); // To avoid race with register in the next setUp
-        setAppOpsModeAllowed(true);
-        setPowerExemption(false);
-    }
-
-    private void setPowerExemption(boolean exempt) throws RemoteException {
-        if (exempt) {
-            mDeviceIdleController.addPowerSaveWhitelistApp(TEST_APP_PACKAGE);
-        } else {
-            mDeviceIdleController.removePowerSaveWhitelistApp(TEST_APP_PACKAGE);
-        }
-    }
-
-    private void setAppOpsModeAllowed(boolean allow) {
-        mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mTestPackageUid,
-                TEST_APP_PACKAGE, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
-    }
-
-    private boolean awaitJobStart(long timeout) throws InterruptedException {
-        return waitUntilTrue(timeout, () -> {
-            synchronized (mTestJobStatus) {
-                return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running;
-            }
-        });
-    }
-
-    private boolean awaitJobStop(long timeout, @JobParameters.StopReason int expectedStopReason)
-            throws InterruptedException {
-        return waitUntilTrue(timeout, () -> {
-            synchronized (mTestJobStatus) {
-                return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running
-                        && (expectedStopReason == JobParameters.STOP_REASON_UNDEFINED
-                        || mTestJobStatus.stopReason == expectedStopReason);
-            }
-        });
-    }
-
-    private boolean waitUntilTrue(long timeout, Condition condition) throws InterruptedException {
-        final long deadLine = SystemClock.uptimeMillis() + timeout;
-        do {
-            Thread.sleep(POLL_INTERVAL);
-        } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
-        return condition.isTrue();
-    }
-
-    private static final class TestJobStatus {
-        int jobId;
-        int stopReason;
-        boolean running;
-
-        private void reset() {
-            running = false;
-            stopReason = jobId = 0;
-        }
-    }
-
-    private interface Condition {
-        boolean isTrue();
-    }
-}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/Android.bp b/services/tests/servicestests/test-apps/JobTestApp/Android.bp
deleted file mode 100644
index 6458bcd..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/Android.bp
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test_helper_app {
-    name: "JobTestApp",
-
-    sdk_version: "current",
-
-    srcs: ["**/*.java"],
-
-    dex_preopt: {
-        enabled: false,
-    },
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
deleted file mode 100644
index ac35805..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.servicestests.apps.jobtestapp">
-
-    <application>
-        <service android:name=".TestJobService"
-                 android:permission="android.permission.BIND_JOB_SERVICE" />
-        <activity android:name=".TestJobActivity"
-                  android:exported="true" />
-    </application>
-
-</manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/OWNERS b/services/tests/servicestests/test-apps/JobTestApp/OWNERS
deleted file mode 100644
index 6f207fb1..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /apex/jobscheduler/OWNERS
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
deleted file mode 100644
index 99eb196..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.servicestests.apps.jobtestapp;
-
-import android.app.Activity;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestJobActivity extends Activity {
-    private static final String TAG = TestJobActivity.class.getSimpleName();
-    private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
-
-    public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
-    public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB";
-    public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
-    public static final int JOB_INITIAL_BACKOFF = 10_000;
-    public static final int JOB_MINIMUM_LATENCY = 5_000;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        ComponentName jobServiceComponent = new ComponentName(this, TestJobService.class);
-        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
-        final Intent intent = getIntent();
-        switch (intent.getAction()) {
-            case ACTION_CANCEL_JOBS:
-                jobScheduler.cancelAll();
-                Log.d(TAG, "Cancelled all jobs for " + getPackageName());
-                break;
-            case ACTION_START_JOB:
-                final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
-                JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
-                        .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
-                        .setMinimumLatency(JOB_MINIMUM_LATENCY)
-                        .setOverrideDeadline(JOB_MINIMUM_LATENCY);
-                final int result = jobScheduler.schedule(jobBuilder.build());
-                if (result != JobScheduler.RESULT_SUCCESS) {
-                    Log.e(TAG, "Could not schedule job " + jobId);
-                } else {
-                    Log.d(TAG, "Successfully scheduled job with id " + jobId);
-                }
-                break;
-            default:
-                Log.e(TAG, "Unknown action " + intent.getAction());
-        }
-        finish();
-    }
-}
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
deleted file mode 100644
index b8585f2..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.servicestests.apps.jobtestapp;
-
-import android.annotation.TargetApi;
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.Intent;
-import android.util.Log;
-
-@TargetApi(24)
-public class TestJobService extends JobService {
-    private static final String TAG = TestJobService.class.getSimpleName();
-    private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
-    public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED";
-    public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED";
-    public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS";
-
-    @Override
-    public boolean onStartJob(JobParameters params) {
-        Log.i(TAG, "Test job executing: " + params.getJobId());
-        Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
-        reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        sendBroadcast(reportJobStartIntent);
-        return true;
-    }
-
-    @Override
-    public boolean onStopJob(JobParameters params) {
-        Log.i(TAG, "Test job stopped executing: " + params.getJobId());
-        Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
-        reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        sendBroadcast(reportJobStopIntent);
-        // Deadline constraint is dropped on reschedule, so it's more reliable to use a new job.
-        return false;
-    }
-}
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 4e1c72a..2f29d10 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -47,6 +47,7 @@
         "flag-junit",
         "notification_flags_lib",
         "platform-test-rules",
+        "SettingsLib",
     ],
 
     libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index db46532..839cf7c 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -17,6 +17,9 @@
 package com.android.server;
 
 import static android.Manifest.permission.MODIFY_DAY_NIGHT_MODE;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
@@ -32,6 +35,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.fail;
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
 
@@ -65,6 +69,7 @@
 import android.Manifest;
 import android.app.Activity;
 import android.app.AlarmManager;
+import android.app.Flags;
 import android.app.IOnProjectionStateChangedListener;
 import android.app.IUiModeManager;
 import android.content.BroadcastReceiver;
@@ -84,6 +89,8 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.test.FakePermissionEnforcer;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
 import android.test.mock.MockContentResolver;
@@ -98,6 +105,7 @@
 
 import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -109,6 +117,7 @@
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Consumer;
 
 @RunWith(AndroidTestingRunner.class)
@@ -159,6 +168,11 @@
     private TwilightListener mTwilightListener;
     private FakePermissionEnforcer mPermissionEnforcer;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+            SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+
+
     @Before
     public void setUp() {
         // The AIDL stub will use PermissionEnforcer to check permission from the caller.
@@ -1437,6 +1451,51 @@
         verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
+    private void testAttentionModeThemeOverlay(boolean modeNight) throws RemoteException {
+        //setup
+        if (modeNight) {
+            mService.setNightMode(MODE_NIGHT_YES);
+            assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
+        } else {
+            mService.setNightMode(MODE_NIGHT_NO);
+            assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
+        }
+
+        // attention modes with expected night modes
+        Map<Integer, Boolean> modes = Map.of(
+                MODE_ATTENTION_THEME_OVERLAY_OFF, modeNight,
+                MODE_ATTENTION_THEME_OVERLAY_DAY, false,
+                MODE_ATTENTION_THEME_OVERLAY_NIGHT, true
+        );
+
+        // test
+        for (int aMode : modes.keySet()) {
+            try {
+                mService.setAttentionModeThemeOverlay(aMode);
+
+                int appliedAMode = mService.getAttentionModeThemeOverlay();
+                boolean nMode = modes.get(aMode);
+
+                assertEquals(aMode, appliedAMode);
+                assertEquals(isNightModeActivated(), nMode);
+            } catch (RemoteException e) {
+                fail("Error communicating with server: " + e.getMessage());
+            }
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testAttentionModeThemeOverlay_nightModeDisabled() throws RemoteException {
+        testAttentionModeThemeOverlay(false);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testAttentionModeThemeOverlay_nightModeEnabled() throws RemoteException {
+        testAttentionModeThemeOverlay(true);
+    }
+
     private void triggerDockIntent() {
         final Intent dockedIntent =
                 new Intent(Intent.ACTION_DOCK_EVENT)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 30843d2..3797dbb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -16,7 +16,8 @@
 
 package com.android.server.notification;
 
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
 import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
 import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
 
@@ -121,28 +122,49 @@
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
         verify(mColorDisplayManager).setSaturationLevel(eq(0));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
-        verify(mUiModeManager).setNightModeActivatedForCustomMode(
-                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
     }
 
     @Test
-    public void apply_removesPreviouslyAppliedEffects() {
+    public void apply_removesEffects() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
 
         ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
                 .setShouldSuppressAmbientDisplay(true)
                 .setShouldDimWallpaper(true)
+                .setShouldDisplayGrayscale(true)
+                .setShouldUseNightMode(true)
                 .build();
         mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+        verify(mColorDisplayManager).setSaturationLevel(eq(0));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
 
         ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
         mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+        verify(mColorDisplayManager).setSaturationLevel(eq(100));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
-        verifyZeroInteractions(mColorDisplayManager, mUiModeManager);
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_OFF));
+    }
+
+    @Test
+    public void apply_removesOnlyPreviouslyAppliedEffects() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
+                .setShouldSuppressAmbientDisplay(true)
+                .build();
+        mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+        verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+
+        ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
+        mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
+
+        verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+        verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
     }
 
     @Test
@@ -150,6 +172,7 @@
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mContext.addMockSystemService(ColorDisplayManager.class, null);
         mContext.addMockSystemService(WallpaperManager.class, null);
+        mApplier = new DefaultDeviceEffectsApplier(mContext);
 
         ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
                 .setShouldSuppressAmbientDisplay(true)
@@ -177,7 +200,7 @@
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
 
         verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean());
-        verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean());
+        verify(mUiModeManager, never()).setAttentionModeThemeOverlay(anyInt());
     }
 
     @Test
@@ -223,8 +246,7 @@
         screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
 
         // So the effect is applied, and we stopped listening for this event.
-        verify(mUiModeManager).setNightModeActivatedForCustomMode(
-                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
         verify(mContext).unregisterReceiver(eq(screenOffReceiver));
     }
 
@@ -239,8 +261,7 @@
                 origin.value());
 
         // Effect was applied, and no broadcast receiver was registered.
-        verify(mUiModeManager).setNightModeActivatedForCustomMode(
-                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
         verify(mContext, never()).registerReceiver(any(), any(), anyInt());
     }
 
@@ -256,8 +277,7 @@
                 origin.value());
 
         // Effect was applied, and no broadcast receiver was registered.
-        verify(mUiModeManager).setNightModeActivatedForCustomMode(
-                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
         verify(mContext, never()).registerReceiver(any(), any(), anyInt());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
index 71dbc57..2986372 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 
@@ -28,7 +29,6 @@
 import org.junit.After;
 import org.junit.Test;
 
-import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -52,18 +52,21 @@
 
     @After
     public void tearDown() {
-        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        mSensitiveContentPackages.clearBlockedApps();
     }
 
     @Test
-    public void setShouldBlockScreenCaptureForApp() {
-        Set<PackageInfo> blockedApps =
+    public void addBlockScreenCaptureForApps() {
+        ArraySet<PackageInfo> blockedApps = new ArraySet(
                 Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
                         new PackageInfo(APP_PKG_1, APP_UID_2),
                         new PackageInfo(APP_PKG_2, APP_UID_1),
-                        new PackageInfo(APP_PKG_2, APP_UID_2));
+                        new PackageInfo(APP_PKG_2, APP_UID_2)
+                ));
 
-        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
+        boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+
+        assertTrue(modified);
 
         assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
         assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
@@ -79,15 +82,93 @@
     }
 
     @Test
-    public void setShouldBlockScreenCaptureForApp_empty() {
-        Set<PackageInfo> blockedApps =
+    public void addBlockScreenCaptureForApps_addedTwice() {
+        ArraySet<PackageInfo> blockedApps = new ArraySet(
                 Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
                         new PackageInfo(APP_PKG_1, APP_UID_2),
                         new PackageInfo(APP_PKG_2, APP_UID_1),
-                        new PackageInfo(APP_PKG_2, APP_UID_2));
+                        new PackageInfo(APP_PKG_2, APP_UID_2)
+                ));
 
-        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
-        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+        boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+
+        assertFalse(modified);
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+    }
+
+    @Test
+    public void addBlockScreenCaptureForApps_withPartialPreviousPackages() {
+        ArraySet<PackageInfo> blockedApps = new ArraySet(
+                Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+                        new PackageInfo(APP_PKG_1, APP_UID_2),
+                        new PackageInfo(APP_PKG_2, APP_UID_1),
+                        new PackageInfo(APP_PKG_2, APP_UID_2)
+                ));
+
+        mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+        boolean modified = mSensitiveContentPackages
+                .addBlockScreenCaptureForApps(
+                        new ArraySet(Set.of(new PackageInfo(APP_PKG_3, APP_UID_1))));
+
+        assertTrue(modified);
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+    }
+
+    @Test
+    public void clearBlockedApps() {
+        ArraySet<PackageInfo> blockedApps = new ArraySet(
+                Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+                        new PackageInfo(APP_PKG_1, APP_UID_2),
+                        new PackageInfo(APP_PKG_2, APP_UID_1),
+                        new PackageInfo(APP_PKG_2, APP_UID_2)
+                ));
+
+        mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+        boolean modified = mSensitiveContentPackages.clearBlockedApps();
+
+        assertTrue(modified);
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+    }
+
+    @Test
+    public void clearBlockedApps_alreadyEmpty() {
+        boolean modified = mSensitiveContentPackages.clearBlockedApps();
+
+        assertFalse(modified);
 
         assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
         assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index a1cc8d5..fe9d837 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -115,7 +115,6 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
-import java.util.Collections;
 
 /**
  * Build/Install/Run:
@@ -139,7 +138,7 @@
 
     @After
     public void tearDown() {
-        mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        mWm.mSensitiveContentPackages.clearBlockedApps();
     }
 
     @Test
@@ -824,7 +823,7 @@
     }
 
     @Test
-    public void setShouldBlockScreenCaptureForApp() {
+    public void addBlockScreenCaptureForApps() {
         String testPackage = "test";
         int ownerId1 = 20;
         int ownerId2 = 21;
@@ -833,7 +832,7 @@
         blockedPackages.add(blockedPackage);
 
         WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
-        wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages);
 
         assertTrue(mWm.mSensitiveContentPackages
                 .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
@@ -843,7 +842,47 @@
     }
 
     @Test
-    public void setShouldBlockScreenCaptureForApp_emptySet_clearsCache() {
+    public void addBlockScreenCaptureForApps_duplicate_verifyNoRefresh() {
+        String testPackage = "test";
+        int ownerId1 = 20;
+        int ownerId2 = 21;
+        PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+        ArraySet<PackageInfo> blockedPackages = new ArraySet();
+        blockedPackages.add(blockedPackage);
+
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+
+        verify(mWm, times(1)).refreshScreenCaptureDisabled();
+    }
+
+    @Test
+    public void addBlockScreenCaptureForApps_notDuplicate_verifyRefresh() {
+        String testPackage = "test";
+        int ownerId1 = 20;
+        int ownerId2 = 21;
+        PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+        PackageInfo blockedPackage2 = new PackageInfo(testPackage, ownerId2);
+        ArraySet<PackageInfo> blockedPackages = new ArraySet();
+        blockedPackages.add(blockedPackage);
+        ArraySet<PackageInfo> blockedPackages2 = new ArraySet();
+        blockedPackages2.add(blockedPackage);
+        blockedPackages2.add(blockedPackage2);
+
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages2);
+
+        assertTrue(mWm.mSensitiveContentPackages
+                .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+        assertTrue(mWm.mSensitiveContentPackages
+                .shouldBlockScreenCaptureForApp(testPackage, ownerId2));
+        verify(mWm, times(2)).refreshScreenCaptureDisabled();
+    }
+
+    @Test
+    public void clearBlockedApps_clearsCache() {
         String testPackage = "test";
         int ownerId1 = 20;
         PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
@@ -851,8 +890,8 @@
         blockedPackages.add(blockedPackage);
 
         WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
-        wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
-        wmInternal.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+        wmInternal.clearBlockedApps();
 
         assertFalse(mWm.mSensitiveContentPackages
                 .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
@@ -860,6 +899,14 @@
     }
 
     @Test
+    public void clearBlockedApps_alreadyEmpty_verifyNoRefresh() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        wmInternal.clearBlockedApps();
+
+        verify(mWm, never()).refreshScreenCaptureDisabled();
+    }
+
+    @Test
     public void testisLetterboxBackgroundMultiColored() {
         assertThat(setupLetterboxConfigurationWithBackgroundType(
                 LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index fb4edfa..a0562aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -136,7 +136,7 @@
 
     @After
     public void tearDown() {
-        mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        mWm.mSensitiveContentPackages.clearBlockedApps();
     }
 
     @Test
@@ -1398,7 +1398,7 @@
         PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
         ArraySet<PackageInfo> blockedPackages = new ArraySet();
         blockedPackages.add(blockedPackage);
-        mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedPackages);
+        mWm.mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedPackages);
 
         assertTrue(window1.isSecureLocked());
         assertFalse(window2.isSecureLocked());
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 08f719e..9fc64fe 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2440,7 +2440,7 @@
             }
 
             return queryEventsHelper(userId, query.getBeginTimeMillis(),
-                    query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
+                    query.getEndTimeMillis(), callingPackage, query.getEventTypes());
         }
 
         @Override
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
index 1df7012..49ad461 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
@@ -62,7 +62,8 @@
             SubscriptionManager subscriptionManager,
             TelephonyManager telephonyManager,
             Callback callback) {
-        mSubscriptionManager = Objects.requireNonNull(subscriptionManager);
+        mSubscriptionManager = Objects.requireNonNull(subscriptionManager)
+                .createForAllUserProfiles();
         mTelephonyManager = Objects.requireNonNull(telephonyManager);
         mCallback = Objects.requireNonNull(callback);
         mSubscriptionManager.addOnSubscriptionsChangedListener(
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 2a6ac98..86eed2f 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -601,8 +601,9 @@
      *
      * @return true if caller has ACCESS_LAST_KNOWN_CELL_ID permission else false.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID)
     public static boolean checkLastKnownCellIdAccessPermission(Context context) {
-        return context.checkCallingOrSelfPermission("android.permission.ACCESS_LAST_KNOWN_CELL_ID")
+        return context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID)
                 == PackageManager.PERMISSION_GRANTED;
     }
 
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
index c902016..57209eb 100644
--- a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
@@ -129,11 +129,15 @@
     public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4;
     public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5;
     public static final int CONNECTION_EVENT_VOLTE_SIP = 6;
-    public static final int CONNECTION_EVENT_VOLTE_RTP = 7;
-    public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 8;
-    public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 9;
-    public static final int CONNECTION_EVENT_VONR_SIP = 10;
-    public static final int CONNECTION_EVENT_VONR_RTP = 11;
+    public static final int CONNECTION_EVENT_VOLTE_SIP_SOS = 7;
+    public static final int CONNECTION_EVENT_VOLTE_RTP = 8;
+    public static final int CONNECTION_EVENT_VOLTE_RTP_SOS = 9;
+    public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 10;
+    public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 11;
+    public static final int CONNECTION_EVENT_VONR_SIP = 12;
+    public static final int CONNECTION_EVENT_VONR_SIP_SOS = 13;
+    public static final int CONNECTION_EVENT_VONR_RTP = 14;
+    public static final int CONNECTION_EVENT_VONR_RTP_SOS = 15;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -141,9 +145,11 @@
             CONNECTION_EVENT_PS_SIGNALLING_GPRS, CONNECTION_EVENT_CS_SIGNALLING_3G,
             CONNECTION_EVENT_PS_SIGNALLING_3G, CONNECTION_EVENT_NAS_SIGNALLING_LTE,
             CONNECTION_EVENT_AS_SIGNALLING_LTE, CONNECTION_EVENT_VOLTE_SIP,
-            CONNECTION_EVENT_VOLTE_RTP, CONNECTION_EVENT_NAS_SIGNALLING_5G,
+            CONNECTION_EVENT_VOLTE_SIP_SOS, CONNECTION_EVENT_VOLTE_RTP,
+            CONNECTION_EVENT_VOLTE_RTP_SOS, CONNECTION_EVENT_NAS_SIGNALLING_5G,
             CONNECTION_EVENT_AS_SIGNALLING_5G, CONNECTION_EVENT_VONR_SIP,
-            CONNECTION_EVENT_VONR_RTP})
+            CONNECTION_EVENT_VONR_SIP_SOS, CONNECTION_EVENT_VONR_RTP,
+            CONNECTION_EVENT_VONR_RTP_SOS})
     public @interface ConnectionEvent {
     }
 
@@ -169,6 +175,7 @@
     public static final int SECURITY_ALGORITHM_NEA1 = 56;
     public static final int SECURITY_ALGORITHM_NEA2 = 57;
     public static final int SECURITY_ALGORITHM_NEA3 = 58;
+    public static final int SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG = 66;
     public static final int SECURITY_ALGORITHM_IMS_NULL = 67;
     public static final int SECURITY_ALGORITHM_SIP_NULL = 68;
     public static final int SECURITY_ALGORITHM_AES_GCM = 69;
@@ -178,6 +185,7 @@
     public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73;
     public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74;
     public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 75;
+    public static final int SECURITY_ALGORITHM_RTP = 85;
     public static final int SECURITY_ALGORITHM_SRTP_NULL = 86;
     public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
     public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
@@ -199,15 +207,16 @@
             SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1,
             SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0,
             SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3,
-            SECURITY_ALGORITHM_IMS_NULL, SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
+            SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG, SECURITY_ALGORITHM_IMS_NULL,
+            SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
             SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC,
             SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC,
             SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_MD5_96,
-            SECURITY_ALGORITHM_SRTP_NULL, SECURITY_ALGORITHM_SRTP_AES_COUNTER,
-            SECURITY_ALGORITHM_SRTP_AES_F8, SECURITY_ALGORITHM_SRTP_HMAC_SHA1,
-            SECURITY_ALGORITHM_ENCR_AES_GCM_16, SECURITY_ALGORITHM_ENCR_AES_CBC,
-            SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128, SECURITY_ALGORITHM_UNKNOWN,
-            SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
+            SECURITY_ALGORITHM_RTP, SECURITY_ALGORITHM_SRTP_NULL,
+            SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8,
+            SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16,
+            SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+            SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
     public @interface SecurityAlgorithm {
     }
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e4ea479..9ec5f7a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -18483,8 +18483,8 @@
 
     /**
      * Get last known cell identity.
-     * Require {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
-     * com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID, otherwise throws SecurityException.
+     * Require appropriate permissions, otherwise throws SecurityException.
+     *
      * If there is current registered network this value will be same as the registered cell
      * identity. If the device goes out of service the previous cell identity is cached and
      * will be returned. If the cache age of the Cell identity is more than 24 hours
@@ -18492,6 +18492,8 @@
      * @return last known cell identity {@CellIdentity}.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     @RequiresPermission(allOf = {Manifest.permission.ACCESS_FINE_LOCATION,
             "com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"})
     public @Nullable CellIdentity getLastKnownCellIdentity() {
@@ -18569,13 +18571,11 @@
      * an overall "device can make voice calls" boolean, they can use {@link
      * ServiceState#getNetworkRegistrationInfo} to check CS registration state.
      *
-     * <p>TODO(b/215240050) In the future, this API will be removed and replaced with a new superset
-     * API to disentangle the "true" {@link ServiceState} meaning of "this is the connection status
-     * to the tower" from IMS registration state and over-the-top voice calling capabilities.
-     *
      * @hide
      */
     @TestApi
+    @SystemApi
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     @RequiresPermission(Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE)
     public void setVoiceServiceStateOverride(boolean hasService) {
         try {
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 527159a..70a9540 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -120,12 +120,13 @@
     path: "src",
 }
 
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
 // ========================================================================
 filegroup {
     name: "android-test-base-current.txt",
     visibility: [
         "//cts/tests/signature/api",
+        "//vendor:__subpackages__",
     ],
     srcs: [
         "api/current.txt",
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 2ff7413..f37d2d1 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -77,12 +77,13 @@
     auto_gen_config: true,
 }
 
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
 // ========================================================================
 filegroup {
     name: "android-test-mock-current.txt",
     visibility: [
         "//cts/tests/signature/api",
+        "//vendor:__subpackages__",
     ],
     srcs: [
         "api/current.txt",
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 13a5dac..21e09d3 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -79,12 +79,13 @@
     ],
 }
 
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
 // ========================================================================
 filegroup {
     name: "android-test-runner-current.txt",
     visibility: [
         "//cts/tests/signature/api",
+        "//vendor:__subpackages__",
     ],
     srcs: [
         "api/current.txt",
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index b0ca4d2..79d3a10 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -17,7 +17,10 @@
 package com.android.server.wm.flicker.rotation
 
 import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
 import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.IComponentMatcher
+import android.tools.common.traces.surfaceflinger.Display
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -57,9 +60,8 @@
     @Test
     open fun appLayerRotates_StartingPos() {
         flicker.assertLayersStart {
-            this.entry.displays.map { display ->
-                this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
-            }
+            val display = getDisplay(testApp)
+            this.visibleRegion(testApp).coversAtLeast(display.layerStackSpace)
         }
     }
 
@@ -68,12 +70,20 @@
     @Test
     open fun appLayerRotates_EndingPos() {
         flicker.assertLayersEnd {
-            this.entry.displays.map { display ->
-                this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
-            }
+            val display = getDisplay(testApp)
+            this.visibleRegion(testApp).coversAtLeast(display.layerStackSpace)
         }
     }
 
+    private fun LayerTraceEntrySubject.getDisplay(componentMatcher: IComponentMatcher): Display {
+        val stackId = this.layer {
+            componentMatcher.layerMatchesAnyOf(it) && it.isVisible
+        }?.layer?.stackId ?: -1
+
+        return this.entry.displays.firstOrNull { it.layerStackId == stackId }
+            ?: error("Unable to find visible layer for $componentMatcher")
+    }
+
     override fun cujCompleted() {
         super.cujCompleted()
         appLayerRotates_StartingPos()
diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java
index 0f04d6a..fa8fdfa 100644
--- a/tests/testables/src/android/testing/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -62,8 +62,9 @@
  */
 public class TestableContext extends ContextWrapper implements TestRule {
 
-    private final TestableContentResolver mTestableContentResolver;
-    private final TestableSettingsProvider mSettingsProvider;
+    private TestableContentResolver mTestableContentResolver;
+    private TestableSettingsProvider mSettingsProvider;
+    private RuntimeException mSettingsProviderFailure;
 
     private ArrayList<MockServiceResolver> mMockServiceResolvers;
     private ArrayMap<String, Object> mMockSystemServices;
@@ -83,12 +84,24 @@
 
     public TestableContext(Context base, LeakCheck check) {
         super(base);
-        mTestableContentResolver = new TestableContentResolver(base);
-        ContentProviderClient settings = base.getContentResolver()
-                .acquireContentProviderClient(Settings.AUTHORITY);
-        mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
-        mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
-        mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+
+        // Configure TestableSettingsProvider when possible; if we fail to initialize some
+        // underlying infrastructure then remember the error and report it later when a test
+        // attempts to interact with it
+        try {
+            ContentProviderClient settings = base.getContentResolver()
+                    .acquireContentProviderClient(Settings.AUTHORITY);
+            mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
+            mTestableContentResolver = new TestableContentResolver(base);
+            mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
+            mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+            mSettingsProviderFailure = null;
+        } catch (Throwable t) {
+            mTestableContentResolver = null;
+            mSettingsProvider = null;
+            mSettingsProviderFailure = new RuntimeException(
+                    "Failed to initialize TestableSettingsProvider", t);
+        }
         mReceiver = check != null ? check.getTracker("receiver") : null;
         mService = check != null ? check.getTracker("service") : null;
         mComponent = check != null ? check.getTracker("component") : null;
@@ -171,11 +184,17 @@
     }
 
     TestableSettingsProvider getSettingsProvider() {
+        if (mSettingsProviderFailure != null) {
+            throw mSettingsProviderFailure;
+        }
         return mSettingsProvider;
     }
 
     @Override
     public TestableContentResolver getContentResolver() {
+        if (mSettingsProviderFailure != null) {
+            throw mSettingsProviderFailure;
+        }
         return mTestableContentResolver;
     }
 
@@ -515,12 +534,16 @@
         return new TestWatcher() {
             @Override
             protected void succeeded(Description description) {
-                mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+                if (mSettingsProvider != null) {
+                    mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+                }
             }
 
             @Override
             protected void failed(Throwable e, Description description) {
-                mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+                if (mSettingsProvider != null) {
+                    mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+                }
             }
         }.apply(base, description);
     }
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 82e40b1..60c25b7 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -32,6 +32,7 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.Field;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -76,7 +77,7 @@
     }
 
     private TestableLooper(TestLooperManager wrapper, Looper l) {
-        mQueueWrapper = wrapper;
+        mQueueWrapper = Objects.requireNonNull(wrapper);
         setupQueue(l);
     }
 
@@ -282,65 +283,94 @@
         return InstrumentationRegistry.getInstrumentation().acquireLooperManager(l);
     }
 
-    private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>();
+    private static final Map<Object, TestableLooperHolder> sLoopers = new ArrayMap<>();
 
     /**
      * For use with {@link RunWithLooper}, used to get the TestableLooper that was
      * automatically created for this test.
      */
     public static TestableLooper get(Object test) {
-        return sLoopers.get(test);
+        final TestableLooperHolder looperHolder = sLoopers.get(test);
+        return (looperHolder != null) ? looperHolder.mTestableLooper : null;
     }
 
     public static void remove(Object test) {
         sLoopers.remove(test);
     }
 
-    static class LooperFrameworkMethod extends FrameworkMethod {
+    /**
+     * Holder object that contains {@link TestableLooper} so that its initialization can be
+     * deferred until a test case is actually run, instead of forcing it to be created at
+     * {@link FrameworkMethod} construction time.
+     *
+     * This deferral is important because some test environments may configure
+     * {@link Looper#getMainLooper()} as part of a {@code Rule} instead of assuming it's globally
+     * initialized and unconditionally available.
+     */
+    private static class TestableLooperHolder {
+        private final boolean mSetAsMain;
+        private final Object mTest;
+
+        private TestableLooper mTestableLooper;
+        private Looper mLooper;
+        private Handler mHandler;
         private HandlerThread mHandlerThread;
 
-        private final TestableLooper mTestableLooper;
-        private final Looper mLooper;
-        private final Handler mHandler;
+        public TestableLooperHolder(boolean setAsMain, Object test) {
+            mSetAsMain = setAsMain;
+            mTest = test;
+        }
 
-        public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) {
-            super(base.getMethod());
+        public void ensureInit() {
+            if (mLooper != null) return;
             try {
-                mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
+                mLooper = mSetAsMain ? Looper.getMainLooper() : createLooper();
                 mTestableLooper = new TestableLooper(mLooper, false);
-                if (!setAsMain) {
-                    mTestableLooper.getLooper().getThread().setName(test.getClass().getName());
+                if (!mSetAsMain) {
+                    mTestableLooper.getLooper().getThread().setName(mTest.getClass().getName());
                 }
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
-            sLoopers.put(test, mTestableLooper);
             mHandler = new Handler(mLooper);
         }
 
-        public LooperFrameworkMethod(TestableLooper other, FrameworkMethod base) {
+        private Looper createLooper() {
+            // TODO: Find way to share these.
+            mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
+            mHandlerThread.start();
+            return mHandlerThread.getLooper();
+        }
+    }
+
+    static class LooperFrameworkMethod extends FrameworkMethod {
+        private TestableLooperHolder mLooperHolder;
+
+        public LooperFrameworkMethod(FrameworkMethod base, TestableLooperHolder looperHolder) {
             super(base.getMethod());
-            mLooper = other.mLooper;
-            mTestableLooper = other;
-            mHandler = Handler.createAsync(mLooper);
+            mLooperHolder = looperHolder;
         }
 
         public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) {
-            if (sLoopers.containsKey(test)) {
-                return new LooperFrameworkMethod(sLoopers.get(test), base);
+            TestableLooperHolder looperHolder = sLoopers.get(test);
+            if (looperHolder == null) {
+                looperHolder = new TestableLooperHolder(setAsMain, test);
+                sLoopers.put(test, looperHolder);
             }
-            return new LooperFrameworkMethod(base, setAsMain, test);
+            return new LooperFrameworkMethod(base, looperHolder);
         }
 
         @Override
         public Object invokeExplosively(Object target, Object... params) throws Throwable {
-            if (Looper.myLooper() == mLooper) {
+            mLooperHolder.ensureInit();
+            if (Looper.myLooper() == mLooperHolder.mLooper) {
                 // Already on the right thread from another statement, just execute then.
                 return super.invokeExplosively(target, params);
             }
-            boolean set = mTestableLooper.mQueueWrapper == null;
+            boolean set = mLooperHolder.mTestableLooper.mQueueWrapper == null;
             if (set) {
-                mTestableLooper.mQueueWrapper = acquireLooperManager(mLooper);
+                mLooperHolder.mTestableLooper.mQueueWrapper = acquireLooperManager(
+                        mLooperHolder.mLooper);
             }
             try {
                 Object[] ret = new Object[1];
@@ -352,11 +382,11 @@
                         throw new LooperException(throwable);
                     }
                 };
-                Message m = Message.obtain(mHandler, execute);
+                Message m = Message.obtain(mLooperHolder.mHandler, execute);
 
                 // Dispatch our message.
                 try {
-                    mTestableLooper.mQueueWrapper.execute(m);
+                    mLooperHolder.mTestableLooper.mQueueWrapper.execute(m);
                 } catch (LooperException e) {
                     throw e.getSource();
                 } catch (RuntimeException re) {
@@ -373,27 +403,20 @@
                 return ret[0];
             } finally {
                 if (set) {
-                    mTestableLooper.mQueueWrapper.release();
-                    mTestableLooper.mQueueWrapper = null;
-                    if (HOLD_MAIN_THREAD && mLooper == Looper.getMainLooper()) {
+                    mLooperHolder.mTestableLooper.mQueueWrapper.release();
+                    mLooperHolder.mTestableLooper.mQueueWrapper = null;
+                    if (HOLD_MAIN_THREAD && mLooperHolder.mLooper == Looper.getMainLooper()) {
                         TestableInstrumentation.releaseMain();
                     }
                 }
             }
         }
 
-        private Looper createLooper() {
-            // TODO: Find way to share these.
-            mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
-            mHandlerThread.start();
-            return mHandlerThread.getLooper();
-        }
-
         @Override
         protected void finalize() throws Throwable {
             super.finalize();
-            if (mHandlerThread != null) {
-                mHandlerThread.quit();
+            if (mLooperHolder.mHandlerThread != null) {
+                mLooperHolder.mHandlerThread.quit();
             }
         }
 
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f056110..1a82021 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -381,9 +381,9 @@
     return true;
   }
 
-  bool FlattenTypeSpec(const ResourceTableTypeView& type,
-                       const std::vector<ResourceTableEntryView>& sorted_entries,
-                       BigBuffer* buffer) {
+  ResTable_typeSpec* FlattenTypeSpec(const ResourceTableTypeView& type,
+                                     const std::vector<ResourceTableEntryView>& sorted_entries,
+                                     BigBuffer* buffer) {
     ChunkWriter type_spec_writer(buffer);
     ResTable_typeSpec* spec_header =
         type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
@@ -391,7 +391,7 @@
 
     if (sorted_entries.empty()) {
       type_spec_writer.Finish();
-      return true;
+      return spec_header;
     }
 
     // We can't just take the size of the vector. There may be holes in the
@@ -427,7 +427,7 @@
       }
     }
     type_spec_writer.Finish();
-    return true;
+    return spec_header;
   }
 
   bool FlattenTypes(BigBuffer* buffer) {
@@ -450,7 +450,8 @@
       expected_type_id++;
       type_pool_.MakeRef(type.named_type.to_string());
 
-      if (!FlattenTypeSpec(type, type.entries, buffer)) {
+      const auto type_spec_header = FlattenTypeSpec(type, type.entries, buffer);
+      if (!type_spec_header) {
         return false;
       }
 
@@ -511,6 +512,10 @@
           return false;
         }
       }
+
+      // And now we can update the type entries count in the typeSpec header.
+      type_spec_header->typesCount = android::util::HostToDevice16(uint16_t(std::min<uint32_t>(
+          config_to_entry_list_map.size(), std::numeric_limits<uint16_t>::max())));
     }
     return true;
   }
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
new file mode 100644
index 0000000..379c4ae
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.okhttp.internalandroidapi;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+
+/**
+ * A domain name service that resolves IP addresses for host names.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Dns {
+    /**
+     * Returns the IP addresses of {@code hostname}, in the order they should
+     * be attempted.
+     *
+     * @hide
+     */
+    List<InetAddress> lookup(String hostname) throws UnknownHostException;
+}