Merge "Add flag to hide "Problem connecting" for Android Auto" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index da15c26..45e33ce 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -636,6 +636,11 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+cc_aconfig_library {
+    name: "aconfig_hardware_flags_c_lib",
+    aconfig_declarations: "android.hardware.flags-aconfig",
+}
+
 // Widget
 aconfig_declarations {
     name: "android.widget.flags-aconfig",
@@ -803,21 +808,6 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
-// OnDeviceIntelligence
-aconfig_declarations {
-    name: "android.app.ondeviceintelligence-aconfig",
-    exportable: true,
-    package: "android.app.ondeviceintelligence.flags",
-    container: "system",
-    srcs: ["core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig"],
-}
-
-java_aconfig_library {
-    name: "android.app.ondeviceintelligence-aconfig-java",
-    aconfig_declarations: "android.app.ondeviceintelligence-aconfig",
-    defaults: ["framework-minus-apex-aconfig-java-defaults"],
-}
-
 // Permissions
 aconfig_declarations {
     name: "android.permission.flags-aconfig",
@@ -999,6 +989,11 @@
 java_aconfig_library {
     name: "android.app.flags-aconfig-java",
     aconfig_declarations: "android.app.flags-aconfig",
+    min_sdk_version: "34",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.nfcservices",
+    ],
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
@@ -1472,6 +1467,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.appwidget.flags-aconfig-java-host",
+    aconfig_declarations: "android.appwidget.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // App
 aconfig_declarations {
     name: "android.server.app.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index a525583b8..529da53 100644
--- a/Android.bp
+++ b/Android.bp
@@ -446,6 +446,9 @@
         default: [
             "framework-platformcrashrecovery.impl",
         ],
+    }) + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), {
+        true: [],
+        default: ["framework-ondeviceintelligence-platform.impl"],
     }),
     sdk_version: "core_platform",
     installable: false,
@@ -489,6 +492,7 @@
     apex_available: ["//apex_available:platform"],
     visibility: [
         "//frameworks/base:__subpackages__",
+        "//packages/modules/NeuralNetworks:__subpackages__",
     ],
     compile_dex: false,
     headers_only: true,
@@ -584,6 +588,9 @@
         default: [
             "framework-platformcrashrecovery-compat-config",
         ],
+    }) + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), {
+        true: [],
+        default: ["framework-ondeviceintelligence-platform-compat-config"],
     }),
 }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index a5a08fb..fe80d1b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1981,7 +1981,12 @@
                     jobStatus.getNumAppliedFlexibleConstraints(),
                     jobStatus.getNumDroppedFlexibleConstraints(),
                     jobStatus.getFilteredTraceTag(),
-                    jobStatus.getFilteredDebugTags());
+                    jobStatus.getFilteredDebugTags(),
+                    jobStatus.getNumAbandonedFailures(),
+                    /* 0 is reserved for UNKNOWN_POLICY */
+                    jobStatus.getJob().getBackoffPolicy() + 1,
+                    shouldUseAggressiveBackoff(jobStatus.getNumAbandonedFailures()));
+
 
             // If the job is immediately ready to run, then we can just immediately
             // put it in the pending list and try to schedule it.  This is especially
@@ -2422,7 +2427,11 @@
                     cancelled.getNumAppliedFlexibleConstraints(),
                     cancelled.getNumDroppedFlexibleConstraints(),
                     cancelled.getFilteredTraceTag(),
-                    cancelled.getFilteredDebugTags());
+                    cancelled.getFilteredDebugTags(),
+                    cancelled.getNumAbandonedFailures(),
+                    /* 0 is reserved for UNKNOWN_POLICY */
+                    cancelled.getJob().getBackoffPolicy() + 1,
+                    shouldUseAggressiveBackoff(cancelled.getNumAbandonedFailures()));
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 909a9b3..2b401c8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -546,7 +546,11 @@
                     job.getNumAppliedFlexibleConstraints(),
                     job.getNumDroppedFlexibleConstraints(),
                     job.getFilteredTraceTag(),
-                    job.getFilteredDebugTags());
+                    job.getFilteredDebugTags(),
+                    job.getNumAbandonedFailures(),
+                    /* 0 is reserved for UNKNOWN_POLICY */
+                    job.getJob().getBackoffPolicy() + 1,
+                    mService.shouldUseAggressiveBackoff(job.getNumAbandonedFailures()));
             sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
             final String sourcePackage = job.getSourcePackageName();
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1681,7 +1685,11 @@
                 completedJob.getNumAppliedFlexibleConstraints(),
                 completedJob.getNumDroppedFlexibleConstraints(),
                 completedJob.getFilteredTraceTag(),
-                completedJob.getFilteredDebugTags());
+                completedJob.getFilteredDebugTags(),
+                completedJob.getNumAbandonedFailures(),
+                /* 0 is reserved for UNKNOWN_POLICY */
+                completedJob.getJob().getBackoffPolicy() + 1,
+                mService.shouldUseAggressiveBackoff(completedJob.getNumAbandonedFailures()));
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER,
                     JobSchedulerService.TRACE_TRACK_NAME, getId());
diff --git a/api/Android.bp b/api/Android.bp
index 7326203..14c2766 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -105,6 +105,13 @@
         default: [
             "framework-platformcrashrecovery",
         ],
+    }) + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), {
+        true: [
+            "framework-ondeviceintelligence",
+        ],
+        default: [
+            "framework-ondeviceintelligence-platform",
+        ],
     }) + select(release_flag("RELEASE_RANGING_STACK"), {
         true: [
             "framework-ranging",
@@ -119,7 +126,12 @@
         "service-permission",
         "service-rkp",
         "service-sdksandbox",
-    ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+    ] + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), {
+        true: [
+            "service-ondeviceintelligence",
+        ],
+        default: [],
+    }) + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
         "true": [
             "service-crashrecovery",
         ],
@@ -478,6 +490,7 @@
         "//frameworks/base/location",
         "//frameworks/base/packages/CrashRecovery/framework",
         "//frameworks/base/nfc",
+        "//packages/modules/NeuralNetworks:__subpackages__",
     ],
     plugins: ["error_prone_android_framework"],
     errorprone: {
diff --git a/api/api.go b/api/api.go
index 5ca24de..e4d783e 100644
--- a/api/api.go
+++ b/api/api.go
@@ -29,6 +29,7 @@
 const virtualization = "framework-virtualization"
 const location = "framework-location"
 const platformCrashrecovery = "framework-platformcrashrecovery"
+const ondeviceintelligence = "framework-ondeviceintelligence-platform"
 
 var core_libraries_modules = []string{art, conscrypt, i18n}
 
@@ -40,7 +41,7 @@
 // APIs.
 // In addition, the modules in this list are allowed to contribute to test APIs
 // stubs.
-var non_updatable_modules = []string{virtualization, location, platformCrashrecovery}
+var non_updatable_modules = []string{virtualization, location, platformCrashrecovery, ondeviceintelligence}
 
 // The intention behind this soong plugin is to generate a number of "merged"
 // API-related modules that would otherwise require a large amount of very
diff --git a/boot/Android.bp b/boot/Android.bp
index 6eead42..eaa984a 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -31,6 +31,7 @@
         "car_bootclasspath_fragment",
         "nfc_apex_bootclasspath_fragment",
         "release_crashrecovery_module",
+        "release_ondevice_intelligence_module",
         "release_package_profiling_module",
     ],
     properties: [
@@ -176,6 +177,15 @@
                 },
             ],
         },
+        release_ondevice_intelligence_module: {
+            fragments: [
+                // only used when ondeviceintelligence is moved to neuralnetworks module
+                {
+                    apex: "com.android.neuralnetworks",
+                    module: "com.android.ondeviceintelligence-bootclasspath-fragment",
+                },
+            ],
+        },
         release_package_profiling_module: {
             fragments: [
                 // only used if profiling is enabled.
diff --git a/core/api/current.txt b/core/api/current.txt
index 0fe8717..d0a2439 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -2201,6 +2201,11 @@
     field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastSpatialDamping;
     field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowEffectDamping;
     field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowSpatialDamping;
+    field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusLarge;
+    field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusMedium;
+    field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusSmall;
+    field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusXlarge;
+    field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusXsmall;
     field public static final int dialog_min_width_major = 17104899; // 0x1050003
     field public static final int dialog_min_width_minor = 17104900; // 0x1050004
     field public static final int notification_large_icon_height = 17104902; // 0x1050006
@@ -6477,6 +6482,7 @@
     method public String getSortKey();
     method public long getTimeoutAfter();
     method public boolean hasImage();
+    method @FlaggedApi("android.app.api_rich_ongoing") public boolean hasPromotableCharacteristics();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
     field public static final int BADGE_ICON_LARGE = 2; // 0x2
@@ -8878,6 +8884,7 @@
     field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
     field public static final int ERROR_DENIED = 1000; // 0x3e8
     field public static final int ERROR_DISABLED = 1002; // 0x3ea
+    field public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; // 0x7d2
     field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
     field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
     field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
@@ -24921,7 +24928,7 @@
     method @Nullable public android.net.Uri getIconUri();
     method @NonNull public String getId();
     method @NonNull public CharSequence getName();
-    method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public java.util.Set<java.lang.String> getRequiredPermissions();
+    method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public java.util.List<java.util.Set<java.lang.String>> getRequiredPermissions();
     method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
     method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public int getSupportedRoutingTypes();
     method public int getType();
@@ -24991,6 +24998,7 @@
     method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
     method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
     method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public android.media.MediaRoute2Info.Builder setRequiredPermissions(@NonNull java.util.Set<java.lang.String>);
+    method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public android.media.MediaRoute2Info.Builder setRequiredPermissions(@NonNull java.util.List<java.util.Set<java.lang.String>>);
     method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
     method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @NonNull public android.media.MediaRoute2Info.Builder setSupportedRoutingTypes(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
@@ -27213,8 +27221,30 @@
   }
 
   public static final class MediaQualityContract.PictureQuality {
+    field public static final String PARAMETER_AUTO_PICTURE_QUALITY_ENABLED = "auto_picture_quality_enabled";
+    field public static final String PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED = "auto_super_resolution_enabled";
+    field public static final String PARAMETER_BLUE_STRETCH = "blue_stretch";
     field public static final String PARAMETER_BRIGHTNESS = "brightness";
+    field public static final String PARAMETER_COLOR_TEMPERATURE = "color_temperature";
+    field public static final String PARAMETER_COLOR_TUNE = "color_tune";
+    field public static final String PARAMETER_COLOR_TUNER_BLUE_GAIN = "color_tuner_blue_gain";
+    field public static final String PARAMETER_COLOR_TUNER_BLUE_OFFSET = "color_tuner_blue_offset";
+    field public static final String PARAMETER_COLOR_TUNER_BRIGHTNESS = "color_tuner_brightness";
+    field public static final String PARAMETER_COLOR_TUNER_GREEN_GAIN = "color_tuner_green_gain";
+    field public static final String PARAMETER_COLOR_TUNER_GREEN_OFFSET = "color_tuner_green_offset";
+    field public static final String PARAMETER_COLOR_TUNER_HUE = "color_tuner_hue";
+    field public static final String PARAMETER_COLOR_TUNER_RED_GAIN = "color_tuner_red_gain";
+    field public static final String PARAMETER_COLOR_TUNER_RED_OFFSET = "color_tuner_red_offset";
+    field public static final String PARAMETER_COLOR_TUNER_SATURATION = "color_tuner_saturation";
     field public static final String PARAMETER_CONTRAST = "contrast";
+    field public static final String PARAMETER_DECONTOUR = "decontour";
+    field public static final String PARAMETER_DYNAMIC_LUMA_CONTROL = "dynamic_luma_control";
+    field public static final String PARAMETER_FILM_MODE = "film_mode";
+    field public static final String PARAMETER_FLESH_TONE = "flesh_tone";
+    field public static final String PARAMETER_GLOBAL_DIMMING = "global_dimming";
+    field public static final String PARAMETER_HUE = "hue";
+    field public static final String PARAMETER_MPEG_NOISE_REDUCTION = "mpeg_noise_reduction";
+    field public static final String PARAMETER_NOISE_REDUCTION = "noise_reduction";
     field public static final String PARAMETER_SATURATION = "saturation";
     field public static final String PARAMETER_SHARPNESS = "sharpness";
   }
@@ -44992,6 +45022,7 @@
     field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
     field public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array";
     field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE = "regional_satellite_earfcn_bundle";
     field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL = "remove_satellite_plmn_in_manual_network_scan_bool";
     field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
     field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
@@ -45005,6 +45036,7 @@
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
     field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_DATA_SUPPORT_MODE_INT = "satellite_data_support_mode_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_DISPLAY_NAME_STRING = "satellite_display_name_string";
     field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING = "satellite_entitlement_app_name_string";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
@@ -45016,6 +45048,8 @@
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL = "satellite_roaming_p2p_sms_supported_bool";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_screen_off_inactivity_timeout_sec_int";
     field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL = "satellite_roaming_turn_off_session_for_emergency_call_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT = "satellite_sos_max_datagram_size_bytes_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY = "satellite_supported_msg_apps_string_array";
     field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
     field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
     field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ce62ccf..b6e2b8a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -328,6 +328,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("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final String READ_SUBSCRIPTION_PLANS = "android.permission.READ_SUBSCRIPTION_PLANS";
     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";
@@ -2276,149 +2277,6 @@
 
 }
 
-package android.app.ondeviceintelligence {
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback {
-    method public void onDownloadCompleted(@NonNull android.os.PersistableBundle);
-    method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle);
-    method public default void onDownloadProgress(long);
-    method public default void onDownloadStarted(long);
-    field public static final int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3; // 0x3
-    field public static final int DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE = 2; // 0x2
-    field public static final int DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE = 1; // 0x1
-    field public static final int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4; // 0x4
-    field public static final int DOWNLOAD_FAILURE_STATUS_UNKNOWN = 0; // 0x0
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Feature implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public android.os.PersistableBundle getFeatureParams();
-    method public int getId();
-    method @Nullable public String getModelName();
-    method @Nullable public String getName();
-    method public int getType();
-    method public int getVariant();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Feature> CREATOR;
-  }
-
-  public static final class Feature.Builder {
-    ctor public Feature.Builder(int);
-    method @NonNull public android.app.ondeviceintelligence.Feature build();
-    method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle);
-    method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String);
-    method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String);
-    method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int);
-    method @NonNull public android.app.ondeviceintelligence.Feature.Builder setVariant(int);
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable {
-    ctor public FeatureDetails(int, @NonNull android.os.PersistableBundle);
-    ctor public FeatureDetails(int);
-    method public int describeContents();
-    method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
-    method public int getFeatureStatus();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
-    field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
-    field public static final int FEATURE_STATUS_DOWNLOADABLE = 1; // 0x1
-    field public static final int FEATURE_STATUS_DOWNLOADING = 2; // 0x2
-    field public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4; // 0x4
-    field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") public final class InferenceInfo implements android.os.Parcelable {
-    method public int describeContents();
-    method public long getEndTimeMillis();
-    method public long getStartTimeMillis();
-    method public long getSuspendedTimeMillis();
-    method public int getUid();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.InferenceInfo> CREATOR;
-  }
-
-  public static final class InferenceInfo.Builder {
-    ctor public InferenceInfo.Builder(int);
-    method @NonNull public android.app.ondeviceintelligence.InferenceInfo build();
-    method @NonNull public android.app.ondeviceintelligence.InferenceInfo.Builder setEndTimeMillis(long);
-    method @NonNull public android.app.ondeviceintelligence.InferenceInfo.Builder setStartTimeMillis(long);
-    method @NonNull public android.app.ondeviceintelligence.InferenceInfo.Builder setSuspendedTimeMillis(long);
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceException extends java.lang.Exception {
-    ctor public OnDeviceIntelligenceException(int, @NonNull String, @NonNull android.os.PersistableBundle);
-    ctor public OnDeviceIntelligenceException(int, @NonNull android.os.PersistableBundle);
-    ctor public OnDeviceIntelligenceException(int, @NonNull String);
-    ctor public OnDeviceIntelligenceException(int);
-    method public int getErrorCode();
-    method @NonNull public android.os.PersistableBundle getErrorParams();
-    field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; // 0x64
-    field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2
-    field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3
-    field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9
-    field public static final int PROCESSING_ERROR_CANCELLED = 7; // 0x7
-    field public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5; // 0x5
-    field public static final int PROCESSING_ERROR_INTERNAL = 14; // 0xe
-    field public static final int PROCESSING_ERROR_IPC_ERROR = 6; // 0x6
-    field public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8; // 0x8
-    field public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4; // 0x4
-    field public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12; // 0xc
-    field public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11; // 0xb
-    field public static final int PROCESSING_ERROR_SAFETY_ERROR = 10; // 0xa
-    field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf
-    field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd
-    field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
-    field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; // 0xc8
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class OnDeviceIntelligenceManager {
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
-    method @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") @NonNull @RequiresPermission(android.Manifest.permission.DUMP) public java.util.List<android.app.ondeviceintelligence.InferenceInfo> getLatestInferenceInfo(long);
-    method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
-    field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
-    field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
-    field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingCallback {
-    method public default void onDataAugmentRequest(@NonNull android.os.Bundle, @NonNull java.util.function.Consumer<android.os.Bundle>);
-    method public void onError(@NonNull android.app.ondeviceintelligence.OnDeviceIntelligenceException);
-    method public void onResult(@NonNull android.os.Bundle);
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
-    ctor public ProcessingSignal();
-    method public void sendSignal(@NonNull android.os.PersistableBundle);
-    method public void setOnProcessingSignalCallback(@NonNull java.util.concurrent.Executor, @Nullable android.app.ondeviceintelligence.ProcessingSignal.OnProcessingSignalCallback);
-  }
-
-  public static interface ProcessingSignal.OnProcessingSignalCallback {
-    method public void onSignalReceived(@NonNull android.os.PersistableBundle);
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingProcessingCallback extends android.app.ondeviceintelligence.ProcessingCallback {
-    method public void onPartialResult(@NonNull android.os.Bundle);
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
-    ctor public TokenInfo(long, @NonNull android.os.PersistableBundle);
-    ctor public TokenInfo(long);
-    method public int describeContents();
-    method public long getCount();
-    method @NonNull public android.os.PersistableBundle getInfoParams();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.TokenInfo> CREATOR;
-  }
-
-}
-
 package android.app.people {
 
   public final class PeopleManager {
@@ -5266,7 +5124,7 @@
 
   @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
     method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close();
-    method @Nullable public android.hardware.contexthub.HubServiceInfo getServiceInfo();
+    method @Nullable public String getServiceDescriptor();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
   }
 
@@ -5319,7 +5177,7 @@
 
   @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
     method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
-    method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable android.hardware.contexthub.HubServiceInfo);
+    method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String);
     method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
   }
 
@@ -6325,7 +6183,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int, @NonNull android.hardware.location.NanoApp);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.NanoAppBinary);
     method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo);
-    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo, @NonNull android.hardware.contexthub.HubServiceInfo);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo, @NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(@NonNull android.hardware.location.ContextHubInfo);
     method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
     method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
@@ -13791,39 +13649,6 @@
 
 }
 
-package android.service.ondeviceintelligence {
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
-    ctor public OnDeviceIntelligenceService();
-    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
-    method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
-    method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
-    method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
-    method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
-    method public abstract void onInferenceServiceConnected();
-    method public abstract void onInferenceServiceDisconnected();
-    method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
-    method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
-    field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
-    ctor public OnDeviceSandboxedInferenceService();
-    method public final void fetchFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
-    method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
-    method public final void getReadOnlyFileDescriptor(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.ParcelFileDescriptor>) throws java.io.FileNotFoundException;
-    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
-    method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
-    method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
-    method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
-    method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
-    field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
-  }
-
-}
-
 package android.service.persistentdata {
 
   @FlaggedApi("android.security.frp_enforcement") public class PersistentDataBlockManager {
@@ -18825,6 +18650,10 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public interface SatelliteDisallowedReasonsCallback {
+    method public void onSatelliteDisallowedReasonsChanged(@NonNull int[]);
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.List<java.lang.Integer> getBands();
@@ -18840,6 +18669,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int[] getSatelliteDisallowedReasons();
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatellitePlmnsForCarrier(int);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -18850,6 +18680,8 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteModemStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForSatelliteDisallowedReasonsChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDisallowedReasonsCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSelectedNbIotSatelliteSubscriptionChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SelectedNbIotSatelliteSubscriptionCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSupportedStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteSupportedStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -18863,7 +18695,10 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAccessConfigurationForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteAccessConfiguration,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteDisplayName(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.CharSequence,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteSubscriberProvisionStatus(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.telephony.satellite.SatelliteSubscriberProvisionStatus>,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSelectedNbIotSatelliteSubscriptionId(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Integer,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
@@ -18875,6 +18710,8 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForModemStateChanged(@NonNull android.telephony.satellite.SatelliteModemStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDisallowedReasonsChanged(@NonNull android.telephony.satellite.SatelliteDisallowedReasonsCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSelectedNbIotSatelliteSubscriptionChanged(@NonNull android.telephony.satellite.SelectedNbIotSatelliteSubscriptionCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSupportedStateChanged(@NonNull android.telephony.satellite.SatelliteSupportedStateCallback);
     field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String ACTION_SATELLITE_START_NON_EMERGENCY_SESSION = "android.telephony.satellite.action.SATELLITE_START_NON_EMERGENCY_SESSION";
     field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED = "android.telephony.satellite.action.SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
@@ -18946,6 +18783,7 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_NO_VALID_SATELLITE_SUBSCRIPTION = 30; // 0x1e
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9
@@ -19048,6 +18886,10 @@
     method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public default void onSendDatagramStateChanged(int, int, int, int);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public interface SelectedNbIotSatelliteSubscriptionCallback {
+    method public void onSelectedNbIotSatelliteSubscriptionChanged(int);
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SystemSelectionSpecifier implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public int[] getBands();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0a806c7..6230a59 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3247,14 +3247,6 @@
 
 }
 
-package android.service.ondeviceintelligence {
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
-    method public void onReady();
-  }
-
-}
-
 package android.service.quickaccesswallet {
 
   public interface QuickAccessWalletClient extends java.io.Closeable {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 69d3e8d..33ba058 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1189,18 +1189,6 @@
         return procState == PROCESS_STATE_FOREGROUND_SERVICE;
     }
 
-    /** @hide Should this process state be considered jank perceptible? */
-    public static final boolean isProcStateJankPerceptible(int procState) {
-        if (Flags.jankPerceptibleNarrow()) {
-            return procState == PROCESS_STATE_PERSISTENT_UI
-                || procState == PROCESS_STATE_TOP
-                || procState == PROCESS_STATE_IMPORTANT_FOREGROUND
-                || procState == PROCESS_STATE_TOP_SLEEPING;
-        } else {
-            return !isProcStateCached(procState);
-        }
-    }
-
     /** @hide requestType for assist context: only basic information. */
     public static final int ASSIST_CONTEXT_BASIC = 0;
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3cc5ff0..27661ce 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3918,7 +3918,12 @@
             if (mLastProcessState == processState) {
                 return;
             }
-            updateVmProcessState(mLastProcessState, processState);
+            // Do not issue a transitional GC if we are transitioning between 2 cached states.
+            // Only update if the state flips between cached and uncached or vice versa
+            if (ActivityManager.isProcStateCached(mLastProcessState)
+                    != ActivityManager.isProcStateCached(processState)) {
+                updateVmProcessState(processState);
+            }
             mLastProcessState = processState;
             if (localLOGV) {
                 Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
@@ -3927,21 +3932,18 @@
         }
     }
 
-    /** Converts a process state to a VM process state. */
-    private static int toVmProcessState(int processState) {
-        final int state = ActivityManager.isProcStateJankPerceptible(processState)
-                ? VM_PROCESS_STATE_JANK_PERCEPTIBLE
-                : VM_PROCESS_STATE_JANK_IMPERCEPTIBLE;
-        return state;
-    }
-
     /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
-    private void updateVmProcessState(int lastProcessState, int newProcessState) {
-        final int state = toVmProcessState(newProcessState);
-        if (lastProcessState == PROCESS_STATE_UNKNOWN
-                || state != toVmProcessState(lastProcessState)) {
-            VMRuntime.getRuntime().updateProcessState(state);
-        }
+    // Currently ART VM only uses state updates for Transitional GC, and thus
+    // this function initiates a Transitional GC for transitions into Cached apps states.
+    private void updateVmProcessState(int processState) {
+        // Only a transition into Cached state should result in a Transitional GC request
+        // to the ART runtime. Update VM state to JANK_IMPERCEPTIBLE in that case.
+        // Note that there are 4 possible cached states currently, all of which are
+        // JANK_IMPERCEPTIBLE from GC point of view.
+        final int state = ActivityManager.isProcStateCached(processState)
+                ? VM_PROCESS_STATE_JANK_IMPERCEPTIBLE
+                : VM_PROCESS_STATE_JANK_PERCEPTIBLE;
+        VMRuntime.getRuntime().updateProcessState(state);
     }
 
     @Override
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a4d8a5c..7e998d9 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3221,7 +3221,6 @@
     /**
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
     public boolean containsCustomViews() {
         return contentView != null
                 || bigContentView != null
@@ -3235,7 +3234,6 @@
     /**
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
     public boolean hasTitle() {
         return extras != null
                 && (!TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE))
@@ -3245,7 +3243,7 @@
     /**
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
     public boolean hasPromotableStyle() {
         final Class<? extends Style> notificationStyle = getNotificationStyle();
 
@@ -3257,11 +3255,16 @@
     }
 
     /**
-     * @hide
+     * Returns whether the notification has all the characteristics that make it eligible for
+     * {@link #FLAG_PROMOTED_ONGOING}. This method does not factor in other criteria such user
+     * preferences for the app or channel. If this returns true, it does not guarantee that the
+     * notification will be assigned FLAG_PROMOTED_ONGOING by the system, but if this returns false,
+     * it will not.
      */
-    @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
     public boolean hasPromotableCharacteristics() {
         return isColorizedRequested()
+                && isOngoingEvent()
                 && hasTitle()
                 && !isGroupSummary()
                 && !containsCustomViews()
@@ -4158,6 +4161,13 @@
     /**
      * @hide
      */
+    public boolean isOngoingEvent() {
+        return (flags & FLAG_ONGOING_EVENT) != 0;
+    }
+
+    /**
+     * @hide
+     */
     public boolean hasCompletedProgress() {
         // not a progress notification; can't be complete
         if (!extras.containsKey(EXTRA_PROGRESS)
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 9bb0ba4..1e971a5 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1294,6 +1294,13 @@
     public static record Args(@NonNull String mModule, @Nullable String mApi,
             int mMaxEntries, boolean mIsolateUids, boolean mTestMode, boolean mCacheNulls) {
 
+        /**
+         * Default values for fields.
+         */
+        public static final int DEFAULT_MAX_ENTRIES = 32;
+        public static final boolean DEFAULT_ISOLATE_UIDS = true;
+        public static final boolean DEFAULT_CACHE_NULLS = false;
+
         // Validation: the module must be one of the known module strings and the maxEntries must
         // be positive.
         public Args {
@@ -1308,10 +1315,10 @@
         public Args(@NonNull String module) {
             this(module,
                     null,       // api
-                    32,         // maxEntries
-                    true,       // isolateUids
+                    DEFAULT_MAX_ENTRIES,
+                    DEFAULT_ISOLATE_UIDS,
                     false,      // testMode
-                    true        // allowNulls
+                    DEFAULT_CACHE_NULLS
                  );
         }
 
@@ -1361,7 +1368,7 @@
      * Burst a property name into module and api.  Throw if the key is invalid.  This method is
      * used in to transition legacy cache constructors to the args constructor.
      */
-    private static Args parseProperty(@NonNull String name) {
+    private static Args argsFromProperty(@NonNull String name) {
         throwIfInvalidCacheKey(name);
         // Strip off the leading well-known prefix.
         String base = name.substring(CACHE_KEY_PREFIX.length() + 1);
@@ -1384,8 +1391,9 @@
      *
      * @hide
      */
+    @Deprecated
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
-        this(parseProperty(propertyName).maxEntries(maxEntries), propertyName, null);
+        this(argsFromProperty(propertyName).maxEntries(maxEntries), propertyName, null);
     }
 
     /**
@@ -1399,9 +1407,10 @@
      * @param cacheName Name of this cache in debug and dumpsys
      * @hide
      */
+    @Deprecated
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
             @NonNull String cacheName) {
-        this(parseProperty(propertyName).maxEntries(maxEntries), cacheName, null);
+        this(argsFromProperty(propertyName).maxEntries(maxEntries), cacheName, null);
     }
 
     /**
@@ -1857,6 +1866,14 @@
     }
 
     /**
+     * Invalidate caches in all processes that have the module and api specified in the args.
+     * @hide
+     */
+    public static void invalidateCache(@NonNull Args args) {
+        invalidateCache(createPropertyName(args.mModule, args.mApi));
+    }
+
+    /**
      * Invalidate PropertyInvalidatedCache caches in all processes that are keyed on
      * {@var name}. This function is synchronous: caches are invalidated upon return.
      *
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f357836e..248e0433 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -42,8 +42,7 @@
 import android.app.contextualsearch.ContextualSearchManager;
 import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
 import android.app.job.JobSchedulerFrameworkInitializer;
-import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceFrameworkInitializer;
 import android.app.people.PeopleManager;
 import android.app.prediction.AppPredictionManager;
 import android.app.role.RoleFrameworkInitializer;
@@ -1692,19 +1691,6 @@
                         throw new ServiceNotFoundException(Context.WEARABLE_SENSING_SERVICE);
                     }});
 
-        registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class,
-                new CachedServiceFetcher<OnDeviceIntelligenceManager>() {
-                    @Override
-                    public OnDeviceIntelligenceManager createService(ContextImpl ctx)
-                            throws ServiceNotFoundException {
-                        IBinder iBinder = ServiceManager.getServiceOrThrow(
-                                Context.ON_DEVICE_INTELLIGENCE_SERVICE);
-                        IOnDeviceIntelligenceManager manager =
-                                IOnDeviceIntelligenceManager.Stub.asInterface(iBinder);
-                        return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager);
-                    }
-                });
-
         registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
                 new CachedServiceFetcher<GrammaticalInflectionManager>() {
                     @Override
@@ -1849,6 +1835,7 @@
             ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers();
             NearbyFrameworkInitializer.registerServiceWrappers();
             OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
+            OnDeviceIntelligenceFrameworkInitializer.registerServiceWrappers();
             DeviceLockFrameworkInitializer.registerServiceWrappers();
             VirtualizationFrameworkInitializer.registerServiceWrappers();
             ConnectivityFrameworkInitializerBaklava.registerServiceWrappers();
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 01cc9d8..76705dc 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -308,12 +308,18 @@
     public boolean isSleeping;
 
     /**
-     * Whether the top activity fillsParent() is false
+     * Whether the top activity fillsParent() is false.
      * @hide
      */
     public boolean isTopActivityTransparent;
 
     /**
+     * Whether fillsParent() is false for every activity in the tasks stack.
+     * @hide
+     */
+    public boolean isActivityStackTransparent;
+
+    /**
      * The last non-fullscreen bounds the task was launched in or resized to.
      * @hide
      */
@@ -489,6 +495,7 @@
                 && parentTaskId == that.parentTaskId
                 && Objects.equals(topActivity, that.topActivity)
                 && isTopActivityTransparent == that.isTopActivityTransparent
+                && isActivityStackTransparent == that.isActivityStackTransparent
                 && Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds)
                 && Objects.equals(capturedLink, that.capturedLink)
                 && capturedLinkTimestamp == that.capturedLinkTimestamp
@@ -567,6 +574,7 @@
         mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
         displayAreaFeatureId = source.readInt();
         isTopActivityTransparent = source.readBoolean();
+        isActivityStackTransparent = source.readBoolean();
         lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
         capturedLink = source.readTypedObject(Uri.CREATOR);
         capturedLinkTimestamp = source.readLong();
@@ -623,6 +631,7 @@
         dest.writeTypedObject(mTopActivityLocusId, flags);
         dest.writeInt(displayAreaFeatureId);
         dest.writeBoolean(isTopActivityTransparent);
+        dest.writeBoolean(isActivityStackTransparent);
         dest.writeTypedObject(lastNonFullscreenBounds, flags);
         dest.writeTypedObject(capturedLink, flags);
         dest.writeLong(capturedLinkTimestamp);
@@ -668,6 +677,7 @@
                 + " locusId=" + mTopActivityLocusId
                 + " displayAreaFeatureId=" + displayAreaFeatureId
                 + " isTopActivityTransparent=" + isTopActivityTransparent
+                + " isActivityStackTransparent=" + isActivityStackTransparent
                 + " lastNonFullscreenBounds=" + lastNonFullscreenBounds
                 + " capturedLink=" + capturedLink
                 + " capturedLinkTimestamp=" + capturedLinkTimestamp
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 21ec585..bea3010 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -53,16 +53,6 @@
 
 flag {
      namespace: "backstage_power"
-     name: "gate_fgs_timeout_anr_behavior"
-     description: "Gate the new behavior where an ANR is thrown once an FGS times out."
-     bug: "339315145"
-     metadata {
-         purpose: PURPOSE_BUGFIX
-     }
-}
-
-flag {
-     namespace: "backstage_power"
      name: "enable_fgs_timeout_crash_behavior"
      description: "Enable the new behavior where the app is crashed once an FGS times out."
      bug: "339526947"
@@ -166,10 +156,3 @@
      bug: "362537357"
      is_exported: true
 }
-
-flag {
-    name: "jank_perceptible_narrow"
-    namespace: "system_performance"
-    description: "Narrow the scope of Jank Perceptible"
-    bug: "304837972"
-}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 361ba73..af035cb 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -343,16 +343,6 @@
 }
 
 flag {
-    name: "dont_write_policy_definition"
-    namespace: "enterprise"
-    description: "Don't write redundant policy-definition-entry tags"
-    bug: "335663055"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "active_admin_cleanup"
     namespace: "enterprise"
     description: "Remove ActiveAdmin from EnforcingAdmin and related cleanups"
diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java
index c8d80d3..d8179c7 100644
--- a/core/java/android/app/appfunctions/AppFunctionException.java
+++ b/core/java/android/app/appfunctions/AppFunctionException.java
@@ -32,8 +32,8 @@
 /**
  * Represents an app function related error.
  *
- * <p>This exception may include an {@link AppFunctionException#getExtras() Bundle}
- * containing additional error-specific metadata.
+ * <p>This exception may include an {@link AppFunctionException#getExtras() Bundle} containing
+ * additional error-specific metadata.
  *
  * <p>The AppFunction SDK can expose structured APIs by packing and unpacking this Bundle.
  */
@@ -85,6 +85,13 @@
     public static final int ERROR_CANCELLED = 2001;
 
     /**
+     * The operation was disallowed by enterprise policy.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+     */
+    public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002;
+
+    /**
      * An unknown error occurred while processing the call in the AppFunctionService.
      *
      * <p>This error is thrown when the service is connected in the remote application but an
@@ -231,7 +238,8 @@
                 ERROR_SYSTEM_ERROR,
                 ERROR_INVALID_ARGUMENT,
                 ERROR_DISABLED,
-                ERROR_CANCELLED
+                ERROR_CANCELLED,
+                ERROR_ENTERPRISE_POLICY_DISALLOWED
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ErrorCode {}
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 529b59a..e8cfd79 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -17,14 +17,14 @@
 
 flag {
     name: "multi_window_screen_context"
-    namespace: "machine_learning"
+    namespace: "sysui_integrations"
     description: "Report screen context and positions for all windows."
     bug: "371065456"
 }
 
 flag {
     name: "contextual_search_window_layer"
-    namespace: "machine_learning"
+    namespace: "sysui_integrations"
     description: "Identify live contextual search UI to exclude from contextual search screenshot."
     bug: "372510690"
 }
\ No newline at end of file
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 8b6840c..7543fa9 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -284,6 +284,13 @@
 }
 
 flag {
+  name: "nm_binder_perf_cache_channels"
+  namespace: "systemui"
+  description: "Use IpcDataCache for notification channel/group lookups"
+  bug: "362981561"
+}
+
+flag {
   name: "no_sbnholder"
   namespace: "systemui"
   description: "removes sbnholder from NLS"
diff --git a/core/java/android/app/ondeviceintelligence/Feature.aidl b/core/java/android/app/ondeviceintelligence/Feature.aidl
deleted file mode 100644
index 18494d7..0000000
--- a/core/java/android/app/ondeviceintelligence/Feature.aidl
+++ /dev/null
@@ -1,22 +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 android.app.ondeviceintelligence;
-
-/**
-  * @hide
-  */
-parcelable Feature;
diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl b/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl
deleted file mode 100644
index 6d70fc4..0000000
--- a/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl
+++ /dev/null
@@ -1,22 +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 android.app.ondeviceintelligence;
-
-/**
-  * @hide
-  */
-parcelable InferenceInfo;
diff --git a/core/java/android/app/ondeviceintelligence/OWNERS b/core/java/android/app/ondeviceintelligence/OWNERS
deleted file mode 100644
index 85e9e65..0000000
--- a/core/java/android/app/ondeviceintelligence/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-# Bug component: 1363385
-
-sandeepbandaru@google.com
-shivanker@google.com
-hackz@google.com
-volnov@google.com
diff --git a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
deleted file mode 100644
index 74a96c8..0000000
--- a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
+++ /dev/null
@@ -1,17 +0,0 @@
-package: "android.app.ondeviceintelligence.flags"
-container: "system"
-
-flag {
-    name: "enable_on_device_intelligence"
-    is_exported: true
-    namespace: "ondeviceintelligence"
-    description: "Make methods on OnDeviceIntelligenceManager available for local inference."
-    bug: "304755128"
-}
-flag {
-    name: "enable_on_device_intelligence_module"
-    is_exported: true
-    namespace: "ondeviceintelligence"
-    description: "Enable migration to mainline module and related changes."
-    bug: "376427781"
-}
\ No newline at end of file
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
index 82c7617..238f1cb 100644
--- a/core/java/android/app/performance.aconfig
+++ b/core/java/android/app/performance.aconfig
@@ -40,7 +40,7 @@
      name: "pic_separate_permission_notifications"
      is_fixed_read_only: true
      description: "Seperate PermissionManager notifications from cache udpates"
-     bug: "373752556"
+     bug: "379699402"
 }
 
 flag {
diff --git a/core/java/android/app/supervision/SupervisionManagerInternal.java b/core/java/android/app/supervision/SupervisionManagerInternal.java
index d571e14..2cf6ae6 100644
--- a/core/java/android/app/supervision/SupervisionManagerInternal.java
+++ b/core/java/android/app/supervision/SupervisionManagerInternal.java
@@ -27,32 +27,41 @@
  */
 public abstract class SupervisionManagerInternal {
     /**
-     * Returns whether supervision is enabled for the specified user
+     * Returns whether the app with given process uid is the active supervision app.
      *
-     * @param userId The user to retrieve the supervision state for
-     * @return whether the user is supervised
+     * <p>Supervision app is considered active when supervision is enabled for the user running the
+     * given process uid.
+     *
+     * @param uid App process uid.
+     * @return Whether the app is the active supervision app.
+     */
+    public abstract boolean isActiveSupervisionApp(int uid);
+
+    /**
+     * Returns whether supervision is enabled for the specified user.
+     *
+     * @param userId The user to retrieve the supervision state for.
+     * @return Whether the user is supervised.
      */
     public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId);
 
-    /**
-     * Returns whether the supervision lock screen needs to be shown.
-     */
+    /** Returns whether the supervision lock screen needs to be shown. */
     public abstract boolean isSupervisionLockscreenEnabledForUser(@UserIdInt int userId);
 
     /**
      * Set whether supervision is enabled for the specified user.
      *
-     * @param userId The user to set the supervision state for
-     * @param enabled Whether or not the user should be supervised
+     * @param userId The user to set the supervision state for.
+     * @param enabled Whether or not the user should be supervised.
      */
     public abstract void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled);
 
     /**
-     * Sets whether the supervision lock screen should be shown for the specified user
+     * Sets whether the supervision lock screen should be shown for the specified user.
      *
-     * @param userId The user set the superivision state for
-     * @param enabled Whether or not the superivision lock screen needs to be shown
-     * @param options Optional configuration parameters for the supervision lock screen
+     * @param userId The user set the superivision state for.
+     * @param enabled Whether or not the superivision lock screen needs to be shown.
+     * @param options Optional configuration parameters for the supervision lock screen.
      */
     public abstract void setSupervisionLockscreenEnabledForUser(
             @UserIdInt int userId, boolean enabled, @Nullable PersistableBundle options);
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index d4f82f6..1b03532 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -24,3 +24,11 @@
   description: "Flag that enables supervision when the supervision app is the profile owner"
   bug: "377261590"
 }
+
+flag {
+  name: "deprecate_dpm_supervision_apis"
+  is_exported: true
+  namespace: "supervision"
+  description: "Flag that deprecates supervision methods in DPM"
+  bug: "382034839"
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 18d8c3a..a6492d3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -55,6 +55,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.net.Uri;
+import android.os.BadParcelableException;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.BundleMerger;
@@ -12402,8 +12403,19 @@
         addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
         if (mExtras != null && !mExtras.isEmpty()) {
             for (String key : mExtras.keySet()) {
-                Object value = mExtras.get(key);
-
+                Object value;
+                try {
+                    value = mExtras.get(key);
+                } catch (BadParcelableException e) {
+                    // This could happen when the key points to a LazyValue whose class cannot be
+                    // found by the classLoader - A nested object more than 1 level deeper who is
+                    // of type of a custom class could trigger this situation. In such case, we
+                    // ignore it since it is not an intent. However, it could be a custom type that
+                    // extends from Intent. If such an object is retrieved later in another
+                    // component, then trying to launch such a custom class object will fail unless
+                    // removeLaunchSecurityProtection() is called before it is launched.
+                    value = null;
+                }
                 if (value instanceof Intent intent && !visited.contains(intent)) {
                     handleNestedIntent(intent, visited, new NestedIntentKey(
                             NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0));
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 44f2a4c..23f1ff8 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -158,6 +158,17 @@
             ]
         },
         {
+            "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases",
+            "options":[
+               {
+                   "exclude-annotation":"androidx.test.filters.FlakyTest"
+               },
+               {
+                   "exclude-annotation":"org.junit.Ignore"
+               }
+            ]
+        },
+        {
             "name": "CtsPackageInstallerCUJUpdateSelfTestCases",
             "options":[
                {
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 68f17e2..d8d4e16 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -114,3 +114,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "credential_manager"
+    name: "propagate_user_context_for_intent_creation"
+    description: "Propagates the user ID in which to find the right OEM UI component to launch"
+    bug: "373711451"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 7efdd6d..1f12bbf 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -107,7 +107,7 @@
                 public void onSessionOpenRequest(
                         int sessionId,
                         HubEndpointInfo initiator,
-                        @Nullable HubServiceInfo serviceInfo)
+                        @Nullable String serviceDescriptor)
                         throws RemoteException {
                     HubEndpointSession activeSession;
                     synchronized (mLock) {
@@ -128,16 +128,16 @@
                                         processSessionOpenRequestResult(
                                                 sessionId,
                                                 initiator,
-                                                serviceInfo,
+                                                serviceDescriptor,
                                                 mLifecycleCallback.onSessionOpenRequest(
-                                                        initiator, serviceInfo)));
+                                                        initiator, serviceDescriptor)));
                     }
                 }
 
                 private void processSessionOpenRequestResult(
                         int sessionId,
                         HubEndpointInfo initiator,
-                        @Nullable HubServiceInfo serviceInfo,
+                        @Nullable String serviceDescriptor,
                         HubEndpointSessionResult result) {
                     if (result == null) {
                         throw new IllegalArgumentException(
@@ -145,7 +145,7 @@
                     }
 
                     if (result.isAccepted()) {
-                        acceptSession(sessionId, initiator, serviceInfo);
+                        acceptSession(sessionId, initiator, serviceDescriptor);
                     } else {
                         Log.i(
                                 TAG,
@@ -162,7 +162,7 @@
                 private void acceptSession(
                         int sessionId,
                         HubEndpointInfo initiator,
-                        @Nullable HubServiceInfo serviceInfo) {
+                        @Nullable String serviceDescriptor) {
                     if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
                         // No longer registered?
                         return;
@@ -187,7 +187,7 @@
                                         HubEndpoint.this,
                                         mAssignedHubEndpointInfo,
                                         initiator,
-                                        serviceInfo);
+                                        serviceDescriptor);
                         try {
                             // oneway call to notify system service that the request is completed
                             mServiceToken.openSessionRequestComplete(sessionId);
@@ -334,7 +334,6 @@
             @Nullable IHubEndpointMessageCallback endpointMessageCallback,
             @NonNull Executor messageCallbackExecutor) {
         mPendingHubEndpointInfo = pendingEndpointInfo;
-
         mLifecycleCallback = endpointLifecycleCallback;
         mLifecycleCallbackExecutor = lifecycleCallbackExecutor;
         mMessageCallback = endpointMessageCallback;
@@ -387,7 +386,7 @@
     }
 
     /** @hide */
-    public void openSession(HubEndpointInfo destinationInfo, @Nullable HubServiceInfo serviceInfo) {
+    public void openSession(HubEndpointInfo destinationInfo, @Nullable String serviceDescriptor) {
         // TODO(b/378974199): Consider refactor these assertions
         if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
             // No longer registered?
@@ -397,7 +396,7 @@
         HubEndpointSession newSession;
         try {
             // Request system service to assign session id.
-            int sessionId = mServiceToken.openSession(destinationInfo, serviceInfo);
+            int sessionId = mServiceToken.openSession(destinationInfo, serviceDescriptor);
 
             // Save the newly created session
             synchronized (mLock) {
@@ -407,7 +406,7 @@
                                 HubEndpoint.this,
                                 destinationInfo,
                                 mAssignedHubEndpointInfo,
-                                serviceInfo);
+                                serviceDescriptor);
                 mActiveSessions.put(sessionId, newSession);
             }
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index b3d65c1..b8af398 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -44,7 +44,7 @@
     @NonNull private final HubEndpoint mHubEndpoint;
     @NonNull private final HubEndpointInfo mInitiator;
     @NonNull private final HubEndpointInfo mDestination;
-    @Nullable private final HubServiceInfo mServiceInfo;
+    @Nullable private final String mServiceDescriptor;
 
     private final AtomicBoolean mIsClosed = new AtomicBoolean(true);
 
@@ -54,12 +54,12 @@
             @NonNull HubEndpoint hubEndpoint,
             @NonNull HubEndpointInfo destination,
             @NonNull HubEndpointInfo initiator,
-            @Nullable HubServiceInfo serviceInfo) {
+            @Nullable String serviceDescriptor) {
         mId = id;
         mHubEndpoint = hubEndpoint;
         mDestination = destination;
         mInitiator = initiator;
-        mServiceInfo = serviceInfo;
+        mServiceDescriptor = serviceDescriptor;
     }
 
     /**
@@ -131,8 +131,8 @@
     }
 
     /**
-     * Get the {@link HubServiceInfo} associated with this session. Null value indicates that there
-     * is no service associated to this session.
+     * Get the service descriptor associated with this session. Null value indicates that there is
+     * no service associated to this session.
      *
      * <p>For hub initiated sessions, the object was previously used in as an argument for open
      * request in {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}.
@@ -141,8 +141,8 @@
      * android.hardware.location.ContextHubManager#openSession}
      */
     @Nullable
-    public HubServiceInfo getServiceInfo() {
-        return mServiceInfo;
+    public String getServiceDescriptor() {
+        return mServiceDescriptor;
     }
 
     @Override
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
index 1c98b4b..b76b227 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -34,11 +34,12 @@
      * Request system service to open a session with a specific destination.
      *
      * @param destination A valid HubEndpointInfo representing the destination.
+     * @param serviceDescriptor An optional descriptor of the service to scope this session to.
      *
      * @throws IllegalArgumentException If the HubEndpointInfo is not valid.
      * @throws IllegalStateException If there are too many opened sessions.
      */
-    int openSession(in HubEndpointInfo destination, in @nullable HubServiceInfo serviceInfo);
+    int openSession(in HubEndpointInfo destination, in @nullable String serviceDescriptor);
 
     /**
      * Request system service to close a specific session
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
index 1ae5fb9..63edda8 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
@@ -29,9 +29,9 @@
      *
      * @param sessionId An integer identifying the session, assigned by the initiator
      * @param initiator HubEndpointInfo representing the requester
-     * @param serviceInfo Nullable HubServiceInfo representing the service associated with this session
+     * @param serviceDescriptor Nullable string representing the service associated with this session
      */
-    void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator, in @nullable HubServiceInfo serviceInfo);
+    void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator, in @nullable String serviceDescriptor);
 
     /**
      * Request from system service to close a specific session
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
index fe449bb..698ed0a 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
+++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
@@ -34,12 +34,12 @@
      * Called when an endpoint is requesting a session be opened with another endpoint.
      *
      * @param requester The {@link HubEndpointInfo} object representing the requester
-     * @param serviceInfo The {@link HubServiceInfo} object representing the service associated with
-     *     this session. Null indicates that there is no service associated with this session.
+     * @param serviceDescriptor A string describing the service associated with this session. Null
+     *     indicates that there is no service associated with this session.
      */
     @NonNull
     HubEndpointSessionResult onSessionOpenRequest(
-            @NonNull HubEndpointInfo requester, @Nullable HubServiceInfo serviceInfo);
+            @NonNull HubEndpointInfo requester, @Nullable String serviceDescriptor);
 
     /**
      * Called when a communication session is opened and ready to be used.
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index 54d0dd0..1f7d426 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -582,6 +582,15 @@
         }
     }
 
+    /** Returns the graph representation of the topology */
+    public DisplayTopologyGraph getGraph() {
+        // TODO(b/364907904): implement
+        return new DisplayTopologyGraph(mPrimaryDisplayId,
+                new DisplayTopologyGraph.DisplayNode[] { new DisplayTopologyGraph.DisplayNode(
+                        mRoot == null ? Display.DEFAULT_DISPLAY : mRoot.mDisplayId,
+                        new DisplayTopologyGraph.AdjacentDisplay[0])});
+    }
+
     /**
      * Tests whether two brightness float values are within a small enough tolerance
      * of each other.
diff --git a/core/java/android/hardware/display/DisplayTopologyGraph.java b/core/java/android/hardware/display/DisplayTopologyGraph.java
new file mode 100644
index 0000000..938e6d1
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayTopologyGraph.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.hardware.display;
+
+/**
+ * Graph of the displays in {@link android.hardware.display.DisplayTopology} tree.
+ *
+ * @hide
+ */
+public record DisplayTopologyGraph(int primaryDisplayId, DisplayNode[] displayNodes) {
+    /**
+     * Display in the topology
+     */
+    public record DisplayNode(
+            int displayId,
+            AdjacentDisplay[] adjacentDisplays) {}
+
+    /**
+     * Edge to adjacent display
+     */
+    public record AdjacentDisplay(
+            // The logical Id of this adjacent display
+            int displayId,
+            // Side of the other display which touches this adjacent display.
+            @DisplayTopology.TreeNode.Position
+            int position,
+            // How many px this display is shifted along the touchingSide, can be negative.
+            float offsetPx) {}
+}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 3a74130..310e1a6 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -1309,16 +1309,16 @@
      *     ContextHubManager#registerEndpoint(HubEndpoint)}.
      * @param destination {@link HubEndpointInfo} object that represents an endpoint from previous
      *     endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}).
-     * @param serviceInfo {@link HubServiceInfo} object that describes the service associated with
-     *     this session. The information will be sent to the destination as part of open request.
+     * @param serviceDescriptor A string that describes the service associated with this session.
+     *     The information will be sent to the destination as part of open request.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
     public void openSession(
             @NonNull HubEndpoint hubEndpoint,
             @NonNull HubEndpointInfo destination,
-            @NonNull HubServiceInfo serviceInfo) {
-        hubEndpoint.openSession(destination, serviceInfo);
+            @NonNull String serviceDescriptor) {
+        hubEndpoint.openSession(destination, serviceDescriptor);
     }
 
     /**
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 9b37533..9badbf8 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -299,9 +299,10 @@
             if (event.hasNoModifiers()) {
                 return false;
             }
-            return event.hasModifiers(KeyEvent.META_CTRL_ON)
-                    || event.hasModifiers(KeyEvent.META_ALT_ON)
-                    || event.hasModifiers(KeyEvent.KEYCODE_FUNCTION);
+            return event.isCtrlPressed()
+                    || event.isAltPressed()
+                    || event.isFunctionPressed()
+                    || event.isMetaPressed();
         }
 
         private boolean needsVerification(KeyEvent event) {
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 8b6da7e..84ca5ed 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -403,7 +403,7 @@
          * increase when the hardware manufacturer provides an OTA update.
          * <p>
          * This constant records the major version of Android. Use {@link
-         * SDK_INT_FULL} if you need to consider the minor version of Android
+         * #SDK_INT_FULL} if you need to consider the minor version of Android
          * as well.
          * <p>
          * Possible values are defined in {@link Build.VERSION_CODES}.
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 804e8fa..11b80ce 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -18,6 +18,8 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.Instrumentation;
@@ -27,11 +29,14 @@
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodRedirect;
 import android.ravenwood.annotation.RavenwoodRedirectionClass;
+import android.ravenwood.annotation.RavenwoodReplace;
+import android.ravenwood.annotation.RavenwoodThrow;
 import android.util.Log;
 import android.util.Printer;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.ravenwood.RavenwoodEnvironment;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -174,6 +179,28 @@
         }
     }
 
+    @RavenwoodReplace
+    private static void throwIfNotTest() {
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread == null) {
+            // Only tests can reach here.
+            return;
+        }
+        final Instrumentation instrumentation = activityThread.getInstrumentation();
+        if (instrumentation == null) {
+            // Only tests can reach here.
+            return;
+        }
+        if (instrumentation.isInstrumenting()) {
+            return;
+        }
+        throw new IllegalStateException("Test-only API called not from a test!");
+    }
+
+    private static void throwIfNotTest$ravenwood() {
+        return;
+    }
+
     private static boolean isInstrumenting() {
         final ActivityThread activityThread = ActivityThread.currentActivityThread();
         if (activityThread == null) {
@@ -203,12 +230,9 @@
 
     private static final class MatchDeliverableMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
-            if (m.when <= when) {
-                return true;
-            }
-            return false;
+            return n.mMessage.when <= when;
         }
     }
     private final MatchDeliverableMessages mMatchDeliverableMessages =
@@ -355,7 +379,7 @@
      * @see OnFileDescriptorEventListener
      * @see #removeOnFileDescriptorEventListener
      */
-    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+    @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
     public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
             @OnFileDescriptorEventListener.Events int events,
             @NonNull OnFileDescriptorEventListener listener) {
@@ -389,7 +413,7 @@
      * @see OnFileDescriptorEventListener
      * @see #addOnFileDescriptorEventListener
      */
-    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+    @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
     public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) {
         if (fd == null) {
             throw new IllegalArgumentException("fd must not be null");
@@ -405,7 +429,7 @@
         }
     }
 
-    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+    @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
     private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
             OnFileDescriptorEventListener listener) {
         final int fdNum = fd.getInt$();
@@ -528,7 +552,7 @@
     /* This is only read/written from the Looper thread. For use with Concurrent MQ */
     private int mNextPollTimeoutMillis;
     private boolean mMessageDirectlyQueued;
-    private Message nextMessage() {
+    private Message nextMessage(boolean peek) {
         int i = 0;
 
         while (true) {
@@ -690,7 +714,7 @@
             if (sState.compareAndSet(this, sStackStateActive, nextOp)) {
                 mMessageCounts.clearCounts();
                 if (found != null) {
-                    if (!removeFromPriorityQueue(found)) {
+                    if (!peek && !removeFromPriorityQueue(found)) {
                         /*
                          * RemoveMessages() might be able to pull messages out from under us
                          * However we can detect that here and just loop around if it happens.
@@ -724,7 +748,7 @@
             mMessageDirectlyQueued = false;
             nativePollOnce(ptr, mNextPollTimeoutMillis);
 
-            Message msg = nextMessage();
+            Message msg = nextMessage(false);
             if (msg != null) {
                 msg.markInUse();
                 return msg;
@@ -1043,8 +1067,9 @@
         }
 
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == null && m.arg1 == mBarrierToken) {
                 return true;
             }
@@ -1247,10 +1272,92 @@
         return true;
     }
 
+    private Message legacyPeekOrPop(boolean peek) {
+        synchronized (this) {
+            // Try to retrieve the next message.  Return if found.
+            final long now = SystemClock.uptimeMillis();
+            Message prevMsg = null;
+            Message msg = mMessages;
+            if (msg != null && msg.target == null) {
+                // Stalled by a barrier.  Find the next asynchronous message in the queue.
+                do {
+                    prevMsg = msg;
+                    msg = msg.next;
+                } while (msg != null && !msg.isAsynchronous());
+            }
+            if (msg != null) {
+                if (now >= msg.when) {
+                    // Got a message.
+                    mBlocked = false;
+                    if (peek) {
+                        return msg;
+                    }
+                    if (prevMsg != null) {
+                        prevMsg.next = msg.next;
+                        if (prevMsg.next == null) {
+                            mLast = prevMsg;
+                        }
+                    } else {
+                        mMessages = msg.next;
+                        if (msg.next == null) {
+                            mLast = null;
+                        }
+                    }
+                    msg.next = null;
+                    msg.markInUse();
+                    if (msg.isAsynchronous()) {
+                        mAsyncMessageCount--;
+                    }
+                    if (TRACE) {
+                        Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+                    }
+                    return msg;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the timestamp of the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery.
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    Long peekWhenForTest() {
+        throwIfNotTest();
+        Message ret;
+        if (mUseConcurrent) {
+            ret = nextMessage(true);
+        } else {
+            ret = legacyPeekOrPop(true);
+        }
+        return ret != null ? ret.when : null;
+    }
+
+    /**
+     * Return the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    @Nullable
+    Message popForTest() {
+        throwIfNotTest();
+        if (mUseConcurrent) {
+            return nextMessage(false);
+        } else {
+            return legacyPeekOrPop(false);
+        }
+    }
+
     private static final class MatchHandlerWhatAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.what == what && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1281,8 +1388,9 @@
 
     private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1314,8 +1422,9 @@
 
     private static final class MatchHandlerRunnableAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.callback == r && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1348,12 +1457,9 @@
 
     private static final class MatchHandler extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
-            if (m.target == h) {
-                return true;
-            }
-            return false;
+            return n.mMessage.target == h;
         }
     }
     private final MatchHandler mMatchHandler = new MatchHandler();
@@ -1531,8 +1637,9 @@
 
     private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1594,8 +1701,9 @@
 
     private static final class MatchHandlerAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1655,8 +1763,9 @@
 
     private static final class MatchHandlerAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1762,7 +1871,7 @@
 
     private static final class MatchAllMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
             return true;
         }
@@ -1774,9 +1883,10 @@
 
     private static final class MatchAllFutureMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
-            if (m.when > when) {
+            final Message m = n.mMessage;
+                    if (m.when > when) {
                 return true;
             }
             return false;
@@ -2482,7 +2592,7 @@
      * This class is used to find matches for hasMessages() and removeMessages()
      */
     private abstract static class MessageCompare {
-        public abstract boolean compareMessage(Message m, Handler h, int what, Object object,
+        public abstract boolean compareMessage(MessageNode n, Handler h, int what, Object object,
                 Runnable r, long when);
     }
 
@@ -2517,7 +2627,7 @@
         MessageNode p = (MessageNode) top;
 
         while (true) {
-            if (compare.compareMessage(p.mMessage, h, what, object, r, when)) {
+            if (compare.compareMessage(p, h, what, object, r, when)) {
                 found = true;
                 if (DEBUG) {
                     Log.d(TAG_C, "stackHasMessages node matches");
@@ -2562,7 +2672,7 @@
         while (iterator.hasNext()) {
             MessageNode msg = iterator.next();
 
-            if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) {
+            if (compare.compareMessage(msg, h, what, object, r, when)) {
                 if (removeMatches) {
                     found = true;
                     if (queue.remove(msg)) {
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index c2a47d7..47778ed 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -16,9 +16,12 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Instrumentation;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodRedirect;
 import android.ravenwood.annotation.RavenwoodRedirectionClass;
@@ -364,6 +367,28 @@
         mPtr = nativeInit();
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static void throwIfNotTest() {
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread == null) {
+            // Only tests can reach here.
+            return;
+        }
+        final Instrumentation instrumentation = activityThread.getInstrumentation();
+        if (instrumentation == null) {
+            // Only tests can reach here.
+            return;
+        }
+        if (instrumentation.isInstrumenting()) {
+            return;
+        }
+        throw new IllegalStateException("Test-only API called not from a test!");
+    }
+
+    private static void throwIfNotTest$ravenwood() {
+        return;
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -384,8 +409,9 @@
 
     private static final class MatchDeliverableMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.when <= when) {
                 return true;
             }
@@ -562,7 +588,7 @@
     private static final AtomicLong mMessagesDelivered = new AtomicLong();
     private boolean mMessageDirectlyQueued;
 
-    private Message nextMessage() {
+    private Message nextMessage(boolean peek) {
         int i = 0;
 
         while (true) {
@@ -724,7 +750,7 @@
             if (sState.compareAndSet(this, sStackStateActive, nextOp)) {
                 mMessageCounts.clearCounts();
                 if (found != null) {
-                    if (!removeFromPriorityQueue(found)) {
+                    if (!peek && !removeFromPriorityQueue(found)) {
                         /*
                          * RemoveMessages() might be able to pull messages out from under us
                          * However we can detect that here and just loop around if it happens.
@@ -993,8 +1019,9 @@
         }
 
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == null && m.arg1 == mBarrierToken) {
                 return true;
             }
@@ -1039,6 +1066,47 @@
         }
     }
 
+    private static final class MatchEarliestMessage extends MessageCompare {
+        MessageNode mEarliest = null;
+
+        @Override
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
+            if (mEarliest == null || mEarliest.mMessage.when > m.when) {
+                mEarliest = n;
+            }
+
+            return false;
+        }
+    }
+
+    /**
+     * Get the timestamp of the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery.
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    Long peekWhenForTest() {
+        throwIfNotTest();
+        Message ret = nextMessage(true);
+        return ret != null ? ret.when : null;
+    }
+
+    /**
+     * Return the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    @Nullable
+    Message popForTest() {
+        throwIfNotTest();
+        return nextMessage(false);
+    }
+
     private StateNode getStateNode(StackNode node) {
         if (node.isMessageNode()) {
             return ((MessageNode) node).mBottomOfStack;
@@ -1058,7 +1126,7 @@
      * This class is used to find matches for hasMessages() and removeMessages()
      */
     private abstract static class MessageCompare {
-        public abstract boolean compareMessage(Message m, Handler h, int what, Object object,
+        public abstract boolean compareMessage(MessageNode n, Handler h, int what, Object object,
                 Runnable r, long when);
     }
 
@@ -1167,8 +1235,9 @@
 
     private static final class MatchHandlerWhatAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.what == what && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1187,8 +1256,9 @@
 
     private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1208,8 +1278,9 @@
 
     private static final class MatchHandlerRunnableAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.callback == r && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1229,8 +1300,9 @@
 
     private static final class MatchHandler extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h) {
                 return true;
             }
@@ -1268,8 +1340,9 @@
 
     private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1287,8 +1360,9 @@
 
     private static final class MatchHandlerAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1305,8 +1379,9 @@
 
     private static final class MatchHandlerAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1324,8 +1399,8 @@
 
     private static final class MatchAllMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
             return true;
         }
     }
@@ -1336,8 +1411,9 @@
 
     private static final class MatchAllFutureMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.when > when) {
                 return true;
             }
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index 8db1567..a2e9314 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -400,10 +400,11 @@
     }
 
     /**
-     * This is a convenience class that encapsulates configuration information for a
-     * cache.  It may be supplied to the cache constructors in lieu of the other
-     * parameters.  The class captures maximum entry count, the module, the key, and the
-     * api.
+     * This is a convenience class that encapsulates configuration information for a cache.  It
+     * may be supplied to the cache constructors in lieu of the other parameters.  The class
+     * captures maximum entry count, the module, the key, and the api.  The key is used to
+     * invalidate the cache and may be shared by different caches.  The api is a user-visible (in
+     * debug) name for the cache.
      *
      * There are three specific use cases supported by this class.
      *
@@ -430,11 +431,8 @@
      * @hide
      */
     public static class Config {
-        private final int mMaxEntries;
-        @IpcDataCacheModule
-        private final String mModule;
-        private final String mApi;
-        private final String mName;
+        final Args mArgs;
+        final String mName;
 
         /**
          * The list of cache names that were created extending this Config.  If
@@ -452,12 +450,20 @@
          */
         private boolean mDisabled = false;
 
+        /**
+         * Fully construct a config.
+         */
+        private Config(@NonNull Args args, @NonNull String name) {
+            mArgs = args;
+            mName = name;
+        }
+
+        /**
+         *
+         */
         public Config(int maxEntries, @NonNull @IpcDataCacheModule String module,
                 @NonNull String api, @NonNull String name) {
-            mMaxEntries = maxEntries;
-            mModule = module;
-            mApi = api;
-            mName = name;
+            this(new Args(module).api(api).maxEntries(maxEntries), name);
         }
 
         /**
@@ -473,7 +479,7 @@
          * the parameter list.
          */
         public Config(@NonNull Config root, @NonNull String api, @NonNull String name) {
-            this(root.maxEntries(), root.module(), api, name);
+            this(root.mArgs.api(api), name);
         }
 
         /**
@@ -481,7 +487,7 @@
          * the parameter list.
          */
         public Config(@NonNull Config root, @NonNull String api) {
-            this(root.maxEntries(), root.module(), api, api);
+            this(root.mArgs.api(api), api);
         }
 
         /**
@@ -490,26 +496,23 @@
          * current process.
          */
         public Config child(@NonNull String name) {
-            final Config result = new Config(this, api(), name);
+            final Config result = new Config(mArgs, name);
             registerChild(name);
             return result;
         }
 
-        public final int maxEntries() {
-            return mMaxEntries;
+        /**
+         * Set the cacheNull behavior.
+         */
+        public Config cacheNulls(boolean enable) {
+            return new Config(mArgs.cacheNulls(enable), mName);
         }
 
-        @IpcDataCacheModule
-        public final @NonNull String module() {
-            return mModule;
-        }
-
-        public final @NonNull String api() {
-            return mApi;
-        }
-
-        public final @NonNull String name() {
-            return mName;
+        /**
+         * Set the isolateUidss behavior.
+         */
+        public Config isolateUids(boolean enable) {
+            return new Config(mArgs.isolateUids(enable), mName);
         }
 
         /**
@@ -532,7 +535,7 @@
          * Invalidate all caches that share this Config's module and api.
          */
         public void invalidateCache() {
-            IpcDataCache.invalidateCache(mModule, mApi);
+            IpcDataCache.invalidateCache(mArgs);
         }
 
         /**
@@ -564,8 +567,7 @@
      * @hide
      */
     public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) {
-      super(new Args(config.module()).maxEntries(config.maxEntries()).api(config.api()),
-          config.name(), computer);
+        super(config.mArgs, config.mName, computer);
     }
 
     /**
diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
index cae82d0..f49acd1 100644
--- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
@@ -16,9 +16,14 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Instrumentation;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodRedirect;
@@ -99,6 +104,28 @@
         mPtr = nativeInit();
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static void throwIfNotTest() {
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread == null) {
+            // Only tests can reach here.
+            return;
+        }
+        final Instrumentation instrumentation = activityThread.getInstrumentation();
+        if (instrumentation == null) {
+            // Only tests can reach here.
+            return;
+        }
+        if (instrumentation.isInstrumenting()) {
+            return;
+        }
+        throw new IllegalStateException("Test-only API called not from a test!");
+    }
+
+    private static void throwIfNotTest$ravenwood() {
+        return;
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -713,6 +740,78 @@
         return true;
     }
 
+    private Message legacyPeekOrPop(boolean peek) {
+        synchronized (this) {
+            // Try to retrieve the next message.  Return if found.
+            final long now = SystemClock.uptimeMillis();
+            Message prevMsg = null;
+            Message msg = mMessages;
+            if (msg != null && msg.target == null) {
+                // Stalled by a barrier.  Find the next asynchronous message in the queue.
+                do {
+                    prevMsg = msg;
+                    msg = msg.next;
+                } while (msg != null && !msg.isAsynchronous());
+            }
+            if (msg != null) {
+                if (now >= msg.when) {
+                    // Got a message.
+                    mBlocked = false;
+                    if (peek) {
+                        return msg;
+                    }
+                    if (prevMsg != null) {
+                        prevMsg.next = msg.next;
+                        if (prevMsg.next == null) {
+                            mLast = prevMsg;
+                        }
+                    } else {
+                        mMessages = msg.next;
+                        if (msg.next == null) {
+                            mLast = null;
+                        }
+                    }
+                    msg.next = null;
+                    msg.markInUse();
+                    if (msg.isAsynchronous()) {
+                        mAsyncMessageCount--;
+                    }
+                    if (TRACE) {
+                        Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+                    }
+                    return msg;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the timestamp of the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery.
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    Long peekWhenForTest() {
+        throwIfNotTest();
+        Message ret = legacyPeekOrPop(true);
+        return ret != null ? ret.when : null;
+    }
+
+    /**
+     * Return the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    @Nullable
+    Message popForTest() {
+        throwIfNotTest();
+        return legacyPeekOrPop(false);
+    }
+
     boolean hasMessages(Handler h, int what, Object object) {
         if (h == null) {
             return false;
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 8aec7eb..9085fe0 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -277,7 +277,8 @@
             if (service != null) {
                 return service;
             } else {
-                return Binder.allowBlocking(getIServiceManager().checkService(name).getBinder());
+                return Binder.allowBlocking(
+                        getIServiceManager().checkService(name).getServiceWithMetadata().service);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "error in checkService", e);
@@ -425,7 +426,8 @@
     private static IBinder rawGetService(String name) throws RemoteException {
         final long start = sStatLogger.getTime();
 
-        final IBinder binder = getIServiceManager().getService2(name).getBinder();
+        final IBinder binder =
+                getIServiceManager().getService2(name).getServiceWithMetadata().service;
 
         final int time = (int) sStatLogger.logDurationStat(Stats.GET_SERVICE, start);
 
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 5a9c878..49b696d 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -61,7 +61,7 @@
     @UnsupportedAppUsage
     public IBinder getService(String name) throws RemoteException {
         // Same as checkService (old versions of servicemanager had both methods).
-        return checkService(name).getBinder();
+        return checkService(name).getServiceWithMetadata().service;
     }
 
     public Service getService2(String name) throws RemoteException {
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 6357baa..2a46738 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -233,6 +233,14 @@
 }
 
 flag {
+    name: "material_shape_tokens"
+    namespace: "systemui"
+    description: "Adding new Material Tokens for M3 Shape (corner radius) Spec"
+    bug: "324928718"
+    is_exported: true
+}
+
+flag {
     name: "message_queue_tail_tracking"
     namespace: "system_performance"
     description: "track tail of message queue."
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index e4a3c9f..25e8a4d 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -213,7 +213,10 @@
     private int mPreferenceHeaderItemResId = 0;
     private boolean mPreferenceHeaderRemoveEmptyIcon = false;
 
+    private boolean mIsBackCallbackRegistered = false;
     private final OnBackInvokedCallback mOnBackInvokedCallback = this::onBackInvoked;
+    private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
+            this::updateBackCallbackRegistrationState;
 
     /**
      * The starting request code given out to preference framework.
@@ -706,6 +709,7 @@
             }
         }
         updateBackCallbackRegistrationState();
+        getFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
     }
 
     @Override
@@ -715,17 +719,25 @@
 
     private void updateBackCallbackRegistrationState() {
         if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) return;
-        if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
-                && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
-            getOnBackInvokedDispatcher()
-                    .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mOnBackInvokedCallback);
-        } else {
+        if ((mCurHeader != null && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null
+                && mSinglePane) || getFragmentManager().getBackStackEntryCount() != 0) {
+            if (!mIsBackCallbackRegistered) {
+                getOnBackInvokedDispatcher()
+                        .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mOnBackInvokedCallback);
+                mIsBackCallbackRegistered = true;
+            }
+        } else if (mIsBackCallbackRegistered) {
             getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+            mIsBackCallbackRegistered = false;
         }
     }
 
     private void onBackInvoked() {
-        if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
+        if (WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)
+                && getFragmentManager().getBackStackEntryCount() != 0) {
+            getFragmentManager().popBackStackImmediate();
+        } else if (mCurHeader != null && mSinglePane
+                && getFragmentManager().getBackStackEntryCount() == 0
                 && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
             mCurHeader = null;
 
@@ -1012,6 +1024,7 @@
 
     @Override
     protected void onDestroy() {
+        getFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
         mHandler.removeMessages(MSG_BIND_PREFERENCES);
         mHandler.removeMessages(MSG_BUILD_HEADERS);
         super.onDestroy();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9935be2..4acb631 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10042,6 +10042,12 @@
                 "minimal_post_processing_allowed";
 
         /**
+         * Whether to mirror the built-in display on all connected displays.
+         * @hide
+         */
+        public static final String MIRROR_BUILT_IN_DISPLAY = "mirror_built_in_display";
+
+        /**
          * No mode switching will happen.
          *
          * @see #MATCH_CONTENT_FRAME_RATE
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 42dbd37..8add9f7 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -97,14 +97,6 @@
 }
 
 flag {
-    name: "prevent_intent_redirect_show_toast_if_nested_keys_not_collected"
-    namespace: "responsible_apis"
-    description: "Prevent intent redirect attacks by showing a toast if not yet collected"
-    bug: "361143368"
-    is_fixed_read_only: true
-}
-
-flag {
     name: "prevent_intent_redirect_show_toast_if_nested_keys_not_collected_r_w"
     namespace: "responsible_apis"
     description: "Prevent intent redirect attacks by showing a toast if not yet collected"
diff --git a/core/java/android/service/ondeviceintelligence/OWNERS b/core/java/android/service/ondeviceintelligence/OWNERS
deleted file mode 100644
index 09774f7..0000000
--- a/core/java/android/service/ondeviceintelligence/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file:/core/java/android/app/ondeviceintelligence/OWNERS
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6d85e75..5da4985 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -766,7 +766,7 @@
      * container.
      */
     @EnforcePermission("MANAGE_APP_TOKENS")
-    void updateDisplayWindowRequestedVisibleTypes(int displayId, int requestedVisibleTypes,
+    void updateDisplayWindowRequestedVisibleTypes(int displayId, int visibleTypes, int mask,
             in @nullable ImeTracker.Token statsToken);
 
     /**
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index df0c5a3..8a10979 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -6540,6 +6540,15 @@
      * Class with information if a node is a range.
      */
     public static final class RangeInfo {
+        /** @hide */
+        @IntDef(prefix = { "RANGE_TYPE_" }, value = {
+                RANGE_TYPE_INT,
+                RANGE_TYPE_FLOAT,
+                RANGE_TYPE_PERCENT,
+                RANGE_TYPE_INDETERMINATE
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface RangeType {}
 
         /** Range type: integer. */
         public static final int RANGE_TYPE_INT = 0;
@@ -6588,7 +6597,7 @@
          * @param current The current value.
          */
         @Deprecated
-        public static RangeInfo obtain(int type, float min, float max, float current) {
+        public static RangeInfo obtain(@RangeType int type, float min, float max, float current) {
             return new RangeInfo(type, min, max, current);
         }
 
@@ -6602,7 +6611,7 @@
          *            maximum.
          * @param current The current value.
          */
-        public RangeInfo(int type, float min, float max, float current) {
+        public RangeInfo(@RangeType int type, float min, float max, float current) {
             mType = type;
             mMin = min;
             mMax = max;
@@ -6618,6 +6627,7 @@
          * @see #RANGE_TYPE_FLOAT
          * @see #RANGE_TYPE_PERCENT
          */
+        @RangeType
         public int getType() {
             return mType;
         }
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index fe936f7..f42c0ec 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -44,10 +44,10 @@
     private @Nullable ActivityManager.RunningTaskInfo mTriggerTask;
 
     /**
-     * If non-null, the task containing the pip activity that participates in this
-     * transition.
+     * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both
+     * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity.
      */
-    private @Nullable ActivityManager.RunningTaskInfo mPipTask;
+    private @Nullable TransitionRequestInfo.PipChange mPipChange;
 
     /** If non-null, a remote-transition associated with the source of this transition. */
     private @Nullable RemoteTransition mRemoteTransition;
@@ -70,7 +70,7 @@
             @WindowManager.TransitionType int type,
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
             @Nullable RemoteTransition remoteTransition) {
-        this(type, triggerTask, null /* pipTask */,
+        this(type, triggerTask, null /* pipChange */,
                 remoteTransition, null /* displayChange */, 0 /* flags */, -1 /* debugId */);
     }
 
@@ -80,7 +80,7 @@
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
             @Nullable RemoteTransition remoteTransition,
             int flags) {
-        this(type, triggerTask, null /* pipTask */,
+        this(type, triggerTask, null /* pipChange */,
                 remoteTransition, null /* displayChange */, flags, -1 /* debugId */);
     }
 
@@ -91,7 +91,7 @@
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionRequestInfo.DisplayChange displayChange,
             int flags) {
-        this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags,
+        this(type, triggerTask, null /* pipChange */, remoteTransition, displayChange, flags,
                 -1 /* debugId */);
     }
 
@@ -103,7 +103,9 @@
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionRequestInfo.DisplayChange displayChange,
             int flags) {
-        this(type, triggerTask, pipTask, remoteTransition, displayChange, flags, -1 /* debugId */);
+        this(type, triggerTask,
+                pipTask != null ? new TransitionRequestInfo.PipChange(pipTask) : null,
+                remoteTransition, displayChange, flags, -1 /* debugId */);
     }
 
     /** @hide */
@@ -252,7 +254,7 @@
         /** @hide */
         @SuppressWarnings({"unchecked", "RedundantCast"})
         @DataClass.Generated.Member
-        protected DisplayChange(@android.annotation.NonNull android.os.Parcel in) {
+        /* package-private */ DisplayChange(@android.annotation.NonNull android.os.Parcel in) {
             // You can override field unparcelling by defining methods like:
             // static FieldType unparcelFieldName(Parcel in) { ... }
 
@@ -289,7 +291,7 @@
         };
 
         @DataClass.Generated(
-                time = 1697564781403L,
+                time = 1733334462577L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
                 inputSignatures = "private final  int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate  int mStartRotation\nprivate  int mEndRotation\nprivate  boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
@@ -302,6 +304,143 @@
 
     }
 
+    @DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false)
+    public static final class PipChange implements Parcelable {
+        // In AE case, we might care about the TF token instead of the task token.
+        @android.annotation.NonNull
+        private WindowContainerToken mTaskFragmentToken;
+
+        @android.annotation.NonNull
+        private ActivityManager.RunningTaskInfo mTaskInfo;
+
+        /** Create empty display-change. */
+        public PipChange(ActivityManager.RunningTaskInfo taskInfo) {
+            mTaskFragmentToken = taskInfo.token;
+            mTaskInfo = taskInfo;
+        }
+
+        /** Create a display-change representing a rotation. */
+        public PipChange(WindowContainerToken taskFragmentToken,
+                ActivityManager.RunningTaskInfo taskInfo) {
+            mTaskFragmentToken = taskFragmentToken;
+            mTaskInfo = taskInfo;
+        }
+
+
+
+        // Code below generated by codegen v1.0.23.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/window/TransitionRequestInfo.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        @DataClass.Generated.Member
+        public @android.annotation.NonNull WindowContainerToken getTaskFragmentToken() {
+            return mTaskFragmentToken;
+        }
+
+        @DataClass.Generated.Member
+        public @android.annotation.NonNull ActivityManager.RunningTaskInfo getTaskInfo() {
+            return mTaskInfo;
+        }
+
+        @DataClass.Generated.Member
+        public @android.annotation.NonNull PipChange setTaskFragmentToken(@android.annotation.NonNull WindowContainerToken value) {
+            mTaskFragmentToken = value;
+            com.android.internal.util.AnnotationValidations.validate(
+                    android.annotation.NonNull.class, null, mTaskFragmentToken);
+            return this;
+        }
+
+        @DataClass.Generated.Member
+        public @android.annotation.NonNull PipChange setTaskInfo(@android.annotation.NonNull ActivityManager.RunningTaskInfo value) {
+            mTaskInfo = value;
+            com.android.internal.util.AnnotationValidations.validate(
+                    android.annotation.NonNull.class, null, mTaskInfo);
+            return this;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public String toString() {
+            // You can override field toString logic by defining methods like:
+            // String fieldNameToString() { ... }
+
+            return "PipChange { " +
+                    "taskFragmentToken = " + mTaskFragmentToken + ", " +
+                    "taskInfo = " + mTaskInfo +
+            " }";
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+            // You can override field parcelling by defining methods like:
+            // void parcelFieldName(Parcel dest, int flags) { ... }
+
+            dest.writeTypedObject(mTaskFragmentToken, flags);
+            dest.writeTypedObject(mTaskInfo, flags);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        /* package-private */ PipChange(@android.annotation.NonNull android.os.Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            WindowContainerToken taskFragmentToken = (WindowContainerToken) in.readTypedObject(WindowContainerToken.CREATOR);
+            ActivityManager.RunningTaskInfo taskInfo = (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+
+            this.mTaskFragmentToken = taskFragmentToken;
+            com.android.internal.util.AnnotationValidations.validate(
+                    android.annotation.NonNull.class, null, mTaskFragmentToken);
+            this.mTaskInfo = taskInfo;
+            com.android.internal.util.AnnotationValidations.validate(
+                    android.annotation.NonNull.class, null, mTaskInfo);
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @android.annotation.NonNull Parcelable.Creator<PipChange> CREATOR
+                = new Parcelable.Creator<PipChange>() {
+            @Override
+            public PipChange[] newArray(int size) {
+                return new PipChange[size];
+            }
+
+            @Override
+            public PipChange createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+                return new PipChange(in);
+            }
+        };
+
+        @DataClass.Generated(
+                time = 1733334462588L,
+                codegenVersion = "1.0.23",
+                sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
+                inputSignatures = "private @android.annotation.NonNull android.window.WindowContainerToken mTaskFragmentToken\nprivate @android.annotation.NonNull android.app.ActivityManager.RunningTaskInfo mTaskInfo\nclass PipChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
+
 
 
 
@@ -326,9 +465,9 @@
      * @param triggerTask
      *   If non-null, the task containing the activity whose lifecycle change (start or
      *   finish) has caused this transition to occur.
-     * @param pipTask
-     *   If non-null, the task containing the pip activity that participates in this
-     *   transition.
+     * @param pipChange
+     *   If non-null, this request might lead to a PiP transition; {@code PipChange} caches both
+     *   {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity.
      * @param remoteTransition
      *   If non-null, a remote-transition associated with the source of this transition.
      * @param displayChange
@@ -344,7 +483,7 @@
     public TransitionRequestInfo(
             @WindowManager.TransitionType int type,
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
-            @Nullable ActivityManager.RunningTaskInfo pipTask,
+            @Nullable TransitionRequestInfo.PipChange pipChange,
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionRequestInfo.DisplayChange displayChange,
             int flags,
@@ -353,7 +492,7 @@
         com.android.internal.util.AnnotationValidations.validate(
                 WindowManager.TransitionType.class, null, mType);
         this.mTriggerTask = triggerTask;
-        this.mPipTask = pipTask;
+        this.mPipChange = pipChange;
         this.mRemoteTransition = remoteTransition;
         this.mDisplayChange = displayChange;
         this.mFlags = flags;
@@ -380,12 +519,12 @@
     }
 
     /**
-     * If non-null, the task containing the pip activity that participates in this
-     * transition.
+     * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both
+     * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity.
      */
     @DataClass.Generated.Member
-    public @Nullable ActivityManager.RunningTaskInfo getPipTask() {
-        return mPipTask;
+    public @Nullable TransitionRequestInfo.PipChange getPipChange() {
+        return mPipChange;
     }
 
     /**
@@ -433,12 +572,12 @@
     }
 
     /**
-     * If non-null, the task containing the pip activity that participates in this
-     * transition.
+     * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both
+     * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity.
      */
     @DataClass.Generated.Member
-    public @android.annotation.NonNull TransitionRequestInfo setPipTask(@android.annotation.NonNull ActivityManager.RunningTaskInfo value) {
-        mPipTask = value;
+    public @android.annotation.NonNull TransitionRequestInfo setPipChange(@android.annotation.NonNull TransitionRequestInfo.PipChange value) {
+        mPipChange = value;
         return this;
     }
 
@@ -471,10 +610,10 @@
         return "TransitionRequestInfo { " +
                 "type = " + typeToString() + ", " +
                 "triggerTask = " + mTriggerTask + ", " +
-                "pipTask = " + mPipTask + ", " +
+                "pipChange = " + mPipChange + ", " +
                 "remoteTransition = " + mRemoteTransition + ", " +
                 "displayChange = " + mDisplayChange + ", " +
-                "flags = " + Integer.toHexString(mFlags) + ", " +
+                "flags = " + mFlags + ", " +
                 "debugId = " + mDebugId +
         " }";
     }
@@ -487,13 +626,13 @@
 
         byte flg = 0;
         if (mTriggerTask != null) flg |= 0x2;
-        if (mPipTask != null) flg |= 0x4;
+        if (mPipChange != null) flg |= 0x4;
         if (mRemoteTransition != null) flg |= 0x8;
         if (mDisplayChange != null) flg |= 0x10;
         dest.writeByte(flg);
         dest.writeInt(mType);
         if (mTriggerTask != null) dest.writeTypedObject(mTriggerTask, flags);
-        if (mPipTask != null) dest.writeTypedObject(mPipTask, flags);
+        if (mPipChange != null) dest.writeTypedObject(mPipChange, flags);
         if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);
         if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags);
         dest.writeInt(mFlags);
@@ -514,7 +653,7 @@
         byte flg = in.readByte();
         int type = in.readInt();
         ActivityManager.RunningTaskInfo triggerTask = (flg & 0x2) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
-        ActivityManager.RunningTaskInfo pipTask = (flg & 0x4) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+        TransitionRequestInfo.PipChange pipChange = (flg & 0x4) == 0 ? null : (TransitionRequestInfo.PipChange) in.readTypedObject(TransitionRequestInfo.PipChange.CREATOR);
         RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
         TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);
         int flags = in.readInt();
@@ -524,7 +663,7 @@
         com.android.internal.util.AnnotationValidations.validate(
                 WindowManager.TransitionType.class, null, mType);
         this.mTriggerTask = triggerTask;
-        this.mPipTask = pipTask;
+        this.mPipChange = pipChange;
         this.mRemoteTransition = remoteTransition;
         this.mDisplayChange = displayChange;
         this.mFlags = flags;
@@ -548,10 +687,10 @@
     };
 
     @DataClass.Generated(
-            time = 1697564781438L,
+            time = 1733334462604L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
-            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\nprivate final  int mDebugId\n  java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.PipChange mPipChange\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\nprivate final  int mDebugId\n  java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 92f9e60..5d4c408 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -267,7 +267,9 @@
         return Flags.useWearMaterial3Ui()
                 && CompatChanges.isChangeEnabled(WEAR_MATERIAL3_ALERTDIALOG)
                 && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
-                && context.getThemeResId() == com.android.internal.R.style.Theme_DeviceDefault;
+                && (context.getThemeResId() == com.android.internal.R.style.Theme_DeviceDefault
+                    || context.getThemeResId()
+                        == com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert);
     }
 
     static boolean canTextInput(View v) {
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index ff08dd2..3e2f301 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -67,6 +67,13 @@
     // ---- Methods below are for use by the status bar policy services ----
     // You need the STATUS_BAR_SERVICE permission
     RegisterStatusBarResult registerStatusBar(IStatusBar callbacks);
+    /**
+     * Registers the status bar for all displays.
+     *
+     * Returns a map of all display IDs (as strings) to their corresponding RegisterStatusBarResult
+     * objects.
+     */
+    Map<String, RegisterStatusBarResult> registerStatusBarForAllDisplays(IStatusBar callbacks);
     void onPanelRevealed(boolean clearNotificationEffects, int numItems);
     void onPanelHidden();
     // Mark current notifications as "seen" and stop ringing, vibrating, blinking.
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 1e965c5d..bda7547 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
+import android.ravenwood.annotation.RavenwoodReplace;
 import android.util.ArraySet;
 import android.util.EmptyArray;
 
@@ -39,6 +40,10 @@
 
 /**
  * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}.
+ * <p>
+ * Test with:
+ * <code>atest FrameworksUtilTests:com.android.internal.util.ArrayUtilsTest</code>
+ * <code>atest FrameworksUtilTestsRavenwood:com.android.internal.util.ArrayUtilsTest</code>
  */
 @android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ArrayUtils {
@@ -85,6 +90,69 @@
     }
 
     /**
+     * This is like <code>new byte[length]</code>, but it allocates the array as non-movable. This
+     * prevents copies of the data from being left on the Java heap as a result of heap compaction.
+     * Use this when the array will contain sensitive data such as a password or cryptographic key
+     * that needs to be wiped from memory when no longer needed. The owner of the array is still
+     * responsible for the zeroization; {@link #zeroize(byte[])} should be used to do so.
+     *
+     * @param length the length of the array to allocate
+     * @return the new array
+     */
+    public static byte[] newNonMovableByteArray(int length) {
+        return (byte[]) VMRuntime.getRuntime().newNonMovableArray(byte.class, length);
+    }
+
+    /**
+     * Like {@link #newNonMovableByteArray(int)}, but allocates a char array.
+     *
+     * @param length the length of the array to allocate
+     * @return the new array
+     */
+    public static char[] newNonMovableCharArray(int length) {
+        return (char[]) VMRuntime.getRuntime().newNonMovableArray(char.class, length);
+    }
+
+    /**
+     * Zeroizes a byte array as securely as possible. Use this when the array contains sensitive
+     * data such as a password or cryptographic key.
+     * <p>
+     * This zeroizes the array in a way that is guaranteed to not be optimized out by the compiler.
+     * If supported by the architecture, it zeroizes the data not just in the L1 data cache but also
+     * in other levels of the memory hierarchy up to and including main memory (but not above that).
+     * <p>
+     * This works on any <code>byte[]</code>, but to ensure that copies of the array aren't left on
+     * the Java heap the array should have been allocated with {@link #newNonMovableByteArray(int)}.
+     * Use on other arrays might also introduce performance anomalies.
+     *
+     * @param array the array to zeroize. If null, this method has no effect.
+     */
+    @RavenwoodReplace public static native void zeroize(byte[] array);
+
+    /**
+     * Replacement of the above method for host-side unit testing that doesn't support JNI yet.
+     */
+    public static void zeroize$ravenwood(byte[] array) {
+        if (array != null) {
+            Arrays.fill(array, (byte) 0);
+        }
+    }
+
+    /**
+     * Like {@link #zeroize(byte[])}, but for char arrays.
+     */
+    @RavenwoodReplace public static native void zeroize(char[] array);
+
+    /**
+     * Replacement of the above method for host-side unit testing that doesn't support JNI yet.
+     */
+    public static void zeroize$ravenwood(char[] array) {
+        if (array != null) {
+            Arrays.fill(array, (char) 0);
+        }
+    }
+
+    /**
      * Checks if the beginnings of two byte arrays are equal.
      *
      * @param array1 the first byte array
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 754f77e7..d49afa7 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -435,9 +435,11 @@
     public void startListeningForLatencyTrackerConfigChanges() {
         final Context context = ActivityThread.currentApplication();
         if (context == null) {
-            if (DEBUG) {
-                Log.d(TAG, "No application for package: " + ActivityThread.currentPackageName());
-            }
+            Log.e(
+                    TAG,
+                    String.format(
+                            "No application for package: %s. Latency Tracker Disabled",
+                            ActivityThread.currentPackageName()));
             return;
         }
         if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
index 38685b6..5177a03 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
@@ -435,10 +435,15 @@
     private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
         refreshViewPort();
 
-        // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
-        // landscape.
-        final int x = Math.clamp(contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
-                mViewPortOnScreen.left, mViewPortOnScreen.right - mPopupWindow.getWidth());
+        final int x;
+        if (mPopupWindow.getWidth() > mViewPortOnScreen.width()) {
+            // Not enough space - prefer to position as far left as possible
+            x = mViewPortOnScreen.left;
+        } else {
+            // Initialize x ensuring that the toolbar isn't rendered behind the system bar insets
+            x = Math.clamp(contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
+                    mViewPortOnScreen.left, mViewPortOnScreen.right - mPopupWindow.getWidth());
+        }
 
         final int y;
 
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b7a7f96..8e3303a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -92,6 +92,7 @@
         "android_view_VelocityTracker.cpp",
         "android_view_VerifiedKeyEvent.cpp",
         "android_view_VerifiedMotionEvent.cpp",
+        "com_android_internal_util_ArrayUtils.cpp",
         "com_android_internal_util_VirtualRefBasePtr.cpp",
         "core_jni_helpers.cpp",
         ":deviceproductinfoconstants_aidl",
@@ -220,6 +221,7 @@
                 "android_hardware_camera2_utils_SurfaceUtils.cpp",
                 "android_hardware_display_DisplayManagerGlobal.cpp",
                 "android_hardware_display_DisplayViewport.cpp",
+                "android_hardware_display_DisplayTopology.cpp",
                 "android_hardware_HardwareBuffer.cpp",
                 "android_hardware_OverlayProperties.cpp",
                 "android_hardware_SensorManager.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ac187b0..78d69f0 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -220,6 +220,7 @@
 extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env);
 extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
 extern int register_com_android_internal_security_VerityUtils(JNIEnv* env);
+extern int register_com_android_internal_util_ArrayUtils(JNIEnv* env);
 extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
 extern int register_android_window_WindowInfosListener(JNIEnv* env);
 extern int register_android_window_ScreenCapture(JNIEnv* env);
@@ -1621,6 +1622,7 @@
         REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer),
         REG_JNI(register_com_android_internal_os_ZygoteInit),
         REG_JNI(register_com_android_internal_security_VerityUtils),
+        REG_JNI(register_com_android_internal_util_ArrayUtils),
         REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
         REG_JNI(register_android_hardware_Camera),
         REG_JNI(register_android_hardware_camera2_CameraMetadata),
diff --git a/core/jni/android_hardware_display_DisplayTopology.cpp b/core/jni/android_hardware_display_DisplayTopology.cpp
new file mode 100644
index 0000000..d9e802d
--- /dev/null
+++ b/core/jni/android_hardware_display_DisplayTopology.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "DisplayTopology-JNI"
+
+#include <android_hardware_display_DisplayTopology.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <utils/Errors.h>
+
+#include "jni_wrappers.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct {
+    jclass clazz;
+    jfieldID primaryDisplayId;
+    jfieldID displayNodes;
+} gDisplayTopologyGraphClassInfo;
+
+static struct {
+    jclass clazz;
+    jfieldID displayId;
+    jfieldID adjacentDisplays;
+} gDisplayTopologyGraphNodeClassInfo;
+
+static struct {
+    jclass clazz;
+    jfieldID displayId;
+    jfieldID position;
+    jfieldID offsetPx;
+} gDisplayTopologyGraphAdjacentDisplayClassInfo;
+
+// ----------------------------------------------------------------------------
+
+status_t android_hardware_display_DisplayTopologyAdjacentDisplay_toNative(
+        JNIEnv* env, jobject adjacentDisplayObj, DisplayTopologyAdjacentDisplay* adjacentDisplay) {
+    adjacentDisplay->displayId = ui::LogicalDisplayId{
+            env->GetIntField(adjacentDisplayObj,
+                             gDisplayTopologyGraphAdjacentDisplayClassInfo.displayId)};
+    adjacentDisplay->position = static_cast<DisplayTopologyPosition>(
+            env->GetIntField(adjacentDisplayObj,
+                             gDisplayTopologyGraphAdjacentDisplayClassInfo.position));
+    adjacentDisplay->offsetPx =
+            env->GetFloatField(adjacentDisplayObj,
+                               gDisplayTopologyGraphAdjacentDisplayClassInfo.offsetPx);
+    return OK;
+}
+
+status_t android_hardware_display_DisplayTopologyGraphNode_toNative(
+        JNIEnv* env, jobject nodeObj,
+        std::unordered_map<ui::LogicalDisplayId, std::vector<DisplayTopologyAdjacentDisplay>>&
+                graph) {
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId{
+            env->GetIntField(nodeObj, gDisplayTopologyGraphNodeClassInfo.displayId)};
+
+    jobjectArray adjacentDisplaysArray = static_cast<jobjectArray>(
+            env->GetObjectField(nodeObj, gDisplayTopologyGraphNodeClassInfo.adjacentDisplays));
+
+    if (adjacentDisplaysArray) {
+        jsize length = env->GetArrayLength(adjacentDisplaysArray);
+        for (jsize i = 0; i < length; i++) {
+            ScopedLocalRef<jobject>
+                    adjacentDisplayObj(env, env->GetObjectArrayElement(adjacentDisplaysArray, i));
+            if (NULL != adjacentDisplayObj.get()) {
+                break; // found null element indicating end of used portion of the array
+            }
+
+            DisplayTopologyAdjacentDisplay adjacentDisplay;
+            android_hardware_display_DisplayTopologyAdjacentDisplay_toNative(env,
+                                                                             adjacentDisplayObj
+                                                                                     .get(),
+                                                                             &adjacentDisplay);
+            graph[displayId].push_back(adjacentDisplay);
+        }
+    }
+    return OK;
+}
+
+DisplayTopologyGraph android_hardware_display_DisplayTopologyGraph_toNative(JNIEnv* env,
+                                                                            jobject topologyObj) {
+    DisplayTopologyGraph topology;
+    topology.primaryDisplayId = ui::LogicalDisplayId{
+            env->GetIntField(topologyObj, gDisplayTopologyGraphClassInfo.primaryDisplayId)};
+
+    jobjectArray nodesArray = static_cast<jobjectArray>(
+            env->GetObjectField(topologyObj, gDisplayTopologyGraphClassInfo.displayNodes));
+
+    if (nodesArray) {
+        jsize length = env->GetArrayLength(nodesArray);
+        for (jsize i = 0; i < length; i++) {
+            ScopedLocalRef<jobject> nodeObj(env, env->GetObjectArrayElement(nodesArray, i));
+            if (NULL != nodeObj.get()) {
+                break; // found null element indicating end of used portion of the array
+            }
+
+            android_hardware_display_DisplayTopologyGraphNode_toNative(env, nodeObj.get(),
+                                                                       topology.graph);
+        }
+    }
+    return topology;
+}
+
+// ----------------------------------------------------------------------------
+
+int register_android_hardware_display_DisplayTopology(JNIEnv* env) {
+    jclass graphClazz = FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph");
+    gDisplayTopologyGraphClassInfo.clazz = MakeGlobalRefOrDie(env, graphClazz);
+
+    gDisplayTopologyGraphClassInfo.primaryDisplayId =
+            GetFieldIDOrDie(env, gDisplayTopologyGraphClassInfo.clazz, "primaryDisplayId", "I");
+    gDisplayTopologyGraphClassInfo.displayNodes =
+            GetFieldIDOrDie(env, gDisplayTopologyGraphClassInfo.clazz, "displayNodes",
+                            "[Landroid/hardware/display/DisplayTopologyGraph$DisplayNode;");
+
+    jclass displayNodeClazz =
+            FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph$DisplayNode");
+    gDisplayTopologyGraphNodeClassInfo.clazz = MakeGlobalRefOrDie(env, displayNodeClazz);
+    gDisplayTopologyGraphNodeClassInfo.displayId =
+            GetFieldIDOrDie(env, gDisplayTopologyGraphNodeClassInfo.clazz, "displayId", "I");
+    gDisplayTopologyGraphNodeClassInfo.adjacentDisplays =
+            GetFieldIDOrDie(env, gDisplayTopologyGraphNodeClassInfo.clazz, "adjacentDisplays",
+                            "[Landroid/hardware/display/DisplayTopologyGraph$AdjacentDisplay;");
+
+    jclass adjacentDisplayClazz =
+            FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph$AdjacentDisplay");
+    gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz =
+            MakeGlobalRefOrDie(env, adjacentDisplayClazz);
+    gDisplayTopologyGraphAdjacentDisplayClassInfo.displayId =
+            GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "displayId",
+                            "I");
+    gDisplayTopologyGraphAdjacentDisplayClassInfo.position =
+            GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "position",
+                            "I");
+    gDisplayTopologyGraphAdjacentDisplayClassInfo.offsetPx =
+            GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "offsetPx",
+                            "F");
+    return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_hardware_display_DisplayTopology.h b/core/jni/android_hardware_display_DisplayTopology.h
new file mode 100644
index 0000000..390191f
--- /dev/null
+++ b/core/jni/android_hardware_display_DisplayTopology.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <input/DisplayTopologyGraph.h>
+
+#include "jni.h"
+
+namespace android {
+
+/**
+ * Copies the contents of a DVM DisplayTopology object to a new native DisplayTopology instance.
+ * Returns DisplayTopology.
+ */
+extern DisplayTopologyGraph android_hardware_display_DisplayTopologyGraph_toNative(
+        JNIEnv* env, jobject eventObj);
+
+} // namespace android
diff --git a/core/jni/com_android_internal_util_ArrayUtils.cpp b/core/jni/com_android_internal_util_ArrayUtils.cpp
new file mode 100644
index 0000000..c706258
--- /dev/null
+++ b/core/jni/com_android_internal_util_ArrayUtils.cpp
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "ArrayUtils"
+
+#include <android-base/logging.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <string.h>
+#include <unistd.h>
+#include <utils/Log.h>
+
+namespace android {
+
+static size_t GetCacheLineSize() {
+    long size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
+    if (size <= 0) {
+        ALOGE("Unable to determine L1 data cache line size. Assuming 32 bytes");
+        return 32;
+    }
+    // The cache line size should always be a power of 2.
+    CHECK((size & (size - 1)) == 0);
+
+    return size;
+}
+
+static void CleanCacheLineContainingAddress(const uint8_t* p) {
+#if defined(__aarch64__)
+    // 'dc cvac' stands for "Data Cache line Clean by Virtual Address to point-of-Coherency".
+    // It writes the cache line back to the "point-of-coherency", i.e. main memory.
+    asm volatile("dc cvac, %0" ::"r"(p));
+#elif defined(__i386__) || defined(__x86_64__)
+    asm volatile("clflush (%0)" ::"r"(p));
+#elif defined(__riscv)
+    // This should eventually work, but it is not ready to be enabled yet:
+    //  1.) The Android emulator needs to add support for zicbom.
+    //  2.) Kernel needs to enable zicbom in usermode.
+    //  3.) Android clang needs to add zicbom to the target.
+    // asm volatile("cbo.clean (%0)" ::"r"(p));
+#elif defined(__arm__)
+    // arm32 has a cacheflush() syscall, but it is undocumented and only flushes the icache.
+    // It is not the same as cacheflush(2) as documented in the Linux man-pages project.
+#else
+#error "Unknown architecture"
+#endif
+}
+
+static void CleanDataCache(const uint8_t* p, size_t buffer_size, size_t cache_line_size) {
+    // Clean the first line that overlaps the buffer.
+    CleanCacheLineContainingAddress(p);
+    // Clean any additional lines that overlap the buffer.  Use cache-line-aligned addresses to
+    // ensure that (a) the last cache line gets flushed, and (b) no cache line is flushed twice.
+    for (size_t i = cache_line_size - ((uintptr_t)p & (cache_line_size - 1)); i < buffer_size;
+         i += cache_line_size) {
+        CleanCacheLineContainingAddress(p + i);
+    }
+}
+
+static void ZeroizePrimitiveArray(JNIEnv* env, jclass clazz, jarray array, size_t component_len) {
+    static const size_t cache_line_size = GetCacheLineSize();
+
+    if (array == nullptr) {
+        return;
+    }
+
+    size_t buffer_size = env->GetArrayLength(array) * component_len;
+    if (buffer_size == 0) {
+        return;
+    }
+
+    // ART guarantees that GetPrimitiveArrayCritical never copies.
+    jboolean isCopy;
+    void* elems = env->GetPrimitiveArrayCritical(array, &isCopy);
+    CHECK(!isCopy);
+
+#ifdef __BIONIC__
+    memset_explicit(elems, 0, buffer_size);
+#else
+    memset(elems, 0, buffer_size);
+#endif
+    // Clean the data cache so that the data gets zeroized in main memory right away.  Without this,
+    // it might not be written to main memory until the cache line happens to be evicted.
+    CleanDataCache(static_cast<const uint8_t*>(elems), buffer_size, cache_line_size);
+
+    env->ReleasePrimitiveArrayCritical(array, elems, /* mode= */ 0);
+}
+
+static void ZeroizeByteArray(JNIEnv* env, jclass clazz, jbyteArray array) {
+    ZeroizePrimitiveArray(env, clazz, array, sizeof(jbyte));
+}
+
+static void ZeroizeCharArray(JNIEnv* env, jclass clazz, jcharArray array) {
+    ZeroizePrimitiveArray(env, clazz, array, sizeof(jchar));
+}
+
+static const JNINativeMethod sMethods[] = {
+        {"zeroize", "([B)V", (void*)ZeroizeByteArray},
+        {"zeroize", "([C)V", (void*)ZeroizeCharArray},
+};
+
+int register_com_android_internal_util_ArrayUtils(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/internal/util/ArrayUtils", sMethods,
+                                    NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 903d08b..be4fb8b 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -179,6 +179,7 @@
         "art-aconfig-flags",
         "ranging_aconfig_flags",
         "aconfig_settingslib_flags",
+        "telephony_flags",
     ],
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d0a5318..6b05690 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7042,6 +7042,13 @@
     <permission android:name="android.permission.MANAGE_SUBSCRIPTION_PLANS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows for reading subscription plan fields for status and end date.
+         @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    -->
+    <permission android:name="android.permission.READ_SUBSCRIPTION_PLANS"
+        android:protectionLevel="signature|privileged"
+        android:featureFlag="com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date" />
+
     <!-- C2DM permission.
          @hide Used internally.
      -->
diff --git a/core/res/res/values-watch/config_material.xml b/core/res/res/values-watch/config_material.xml
index 8e9693a..73a7c09 100644
--- a/core/res/res/values-watch/config_material.xml
+++ b/core/res/res/values-watch/config_material.xml
@@ -69,4 +69,14 @@
     <integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
     <item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
     <integer name="config_motionExpressiveSlowEffectStiffness">260</integer>
+
+    <!--
+        Material rounded corner configs
+        Values from https://carbon.googleplex.com/wear-m3/tokens/designSystems/70fbaa4f7722a3d1/tokenSets/4fa2518eaeaf65eb
+    -->
+    <dimen name="config_shapeCornerRadiusXsmall">4dp</dimen>
+    <dimen name="config_shapeCornerRadiusSmall">8dp</dimen>
+    <dimen name="config_shapeCornerRadiusMedium">18dp</dimen>
+    <dimen name="config_shapeCornerRadiusLarge">26dp</dimen>
+    <dimen name="config_shapeCornerRadiusXlarge">36dp</dimen>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 13c125c..53b47622 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4228,13 +4228,12 @@
          must match the value of config_cameraLaunchGestureSensorType in OEM's HAL -->
     <string translatable="false" name="config_cameraLaunchGestureSensorStringType"></string>
 
-    <!-- Allow the gesture to double tap the power button to trigger a target action. -->
-    <bool name="config_doubleTapPowerGestureEnabled">true</bool>
     <!-- Allow the gesture to double tap the power button twice to start the camera while the device
          is non-interactive. -->
     <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
-    <!-- Allow the gesture to double tap the power button twice to launch the wallet. -->
-    <bool name="config_walletDoubleTapPowerGestureEnabled">true</bool>
+
+    <!-- Allow the gesture to double tap the power button to trigger a target action. -->
+    <bool name="config_doubleTapPowerGestureEnabled">true</bool>
     <!-- Default target action for double tap of the power button gesture.
          0: Launch camera
          1: Launch wallet -->
diff --git a/core/res/res/values/config_material.xml b/core/res/res/values/config_material.xml
index 6034f9c..648fe90 100644
--- a/core/res/res/values/config_material.xml
+++ b/core/res/res/values/config_material.xml
@@ -75,4 +75,14 @@
     <integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
     <item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
     <integer name="config_motionExpressiveSlowEffectStiffness">800</integer>
+
+    <!--
+        Material rounded corner configs
+        Values from https://carbon.googleplex.com/google-material-3/tokens/designSystems/20543ce18892f7d9/tokenSets/21c40db4e4f5af15
+    -->
+    <dimen name="config_shapeCornerRadiusXsmall">4dp</dimen>
+    <dimen name="config_shapeCornerRadiusSmall">8dp</dimen>
+    <dimen name="config_shapeCornerRadiusMedium">12dp</dimen>
+    <dimen name="config_shapeCornerRadiusLarge">16dp</dimen>
+    <dimen name="config_shapeCornerRadiusXlarge">28dp</dimen>
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 7b9d213..6c73b0c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -184,6 +184,16 @@
     <public name="config_motionExpressiveSlowSpatialDamping"/>
     <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
     <public name="config_motionExpressiveSlowEffectDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+    <public name="config_shapeCornerRadiusXsmall"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+    <public name="config_shapeCornerRadiusSmall"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+    <public name="config_shapeCornerRadiusMedium"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+    <public name="config_shapeCornerRadiusLarge"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+    <public name="config_shapeCornerRadiusXlarge"/>
   </staging-public-group>
 
   <staging-public-group type="color" first-id="0x01b20000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2671ff9..28de553 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3161,9 +3161,8 @@
   <java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" />
   <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
   <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
-  <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" />
   <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
-  <java-symbol type="bool" name="config_walletDoubleTapPowerGestureEnabled" />
+  <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" />
   <java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" />
   <java-symbol type="bool" name="config_emergencyGestureEnabled" />
   <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
@@ -5878,4 +5877,12 @@
   <!-- List of protected packages that require biometric authentication for modification -->
   <java-symbol type="array" name="config_biometric_protected_package_names" />
 
+  <!-- Material shape spec config tokens -->
+  <java-symbol type="dimen" name="config_shapeCornerRadiusXsmall"/>
+  <java-symbol type="dimen" name="config_shapeCornerRadiusSmall"/>
+  <java-symbol type="dimen" name="config_shapeCornerRadiusMedium"/>
+  <java-symbol type="dimen" name="config_shapeCornerRadiusLarge"/>
+  <java-symbol type="dimen" name="config_shapeCornerRadiusXlarge"/>
+
+
 </resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 63e678d..9effeec 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -467,12 +467,25 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .build();
         assertThat(n.hasPromotableCharacteristics()).isTrue();
     }
 
     @Test
     @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableCharacteristics_notOngoing() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .build();
+        assertThat(n.hasPromotableCharacteristics()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
     public void testHasPromotableCharacteristics_wrongStyle() {
         Notification n = new Notification.Builder(mContext, "test")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -480,6 +493,7 @@
                 .setContentTitle("TITLE")
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .build();
         assertThat(n.hasPromotableCharacteristics()).isFalse();
     }
@@ -491,6 +505,7 @@
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
+                .setOngoing(true)
                 .build();
         assertThat(n.hasPromotableCharacteristics()).isFalse();
     }
@@ -503,6 +518,7 @@
                 .setStyle(new Notification.BigTextStyle())
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .build();
         assertThat(n.hasPromotableCharacteristics()).isFalse();
     }
@@ -515,6 +531,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .setGroup("someGroup")
                 .setGroupSummary(true)
                 .build();
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index 177c7f0..bd27337 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -740,5 +740,20 @@
         assertEquals(null, cache.query(30));
         // The recompute is 4 because nulls were not cached.
         assertEquals(4, cache.getRecomputeCount());
+
+        // Verify that the default is not to cache nulls.
+        cache = new TestCache(new Args(MODULE_TEST)
+                .maxEntries(4).api("testCachingNulls"),
+                new TestQuery());
+        cache.invalidateCache();
+        assertEquals("foo1", cache.query(1));
+        assertEquals("foo2", cache.query(2));
+        assertEquals(null, cache.query(30));
+        assertEquals(3, cache.getRecomputeCount());
+        assertEquals("foo1", cache.query(1));
+        assertEquals("foo2", cache.query(2));
+        assertEquals(null, cache.query(30));
+        // The recompute is 4 because nulls were not cached.
+        assertEquals(4, cache.getRecomputeCount());
     }
 }
diff --git a/core/tests/coretests/src/android/app/QueuedWorkTest.java b/core/tests/coretests/src/android/app/QueuedWorkTest.java
index 230c9e8..6bd9b6a 100644
--- a/core/tests/coretests/src/android/app/QueuedWorkTest.java
+++ b/core/tests/coretests/src/android/app/QueuedWorkTest.java
@@ -163,18 +163,18 @@
 
     @Test
     public void testHasPendingWork() {
-        Semaphore releaser = new Semaphore(0);
-        mQueuedWork.queue(
-                () -> {
-                    try {
-                        releaser.acquire();
-                    } catch (InterruptedException e) {
-                        throw new RuntimeException(e);
-                    }
-                }, false);
+        final Semaphore releaser1 = new Semaphore(0);
+        final Semaphore releaser2 = new Semaphore(0);
+        mQueuedWork.queue(() -> releaser1.acquireUninterruptibly(), false);
+        mQueuedWork.queue(() -> releaser2.release(), false);
+        // Worker should be waiting for releaser1,
+        // and have pending work to release releaser2
         assertThat(mQueuedWork.hasPendingWork()).isTrue();
-        releaser.release();
-        mQueuedWork.waitToFinish();
+
+        // Allow worker to get to releasing releaser2
+        releaser1.release();
+        releaser2.acquireUninterruptibly();
+        // If we got here then there is no pending work.
         assertThat(mQueuedWork.hasPendingWork()).isFalse();
     }
 }
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
index e14608a..bb8356f 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -16,13 +16,21 @@
 
 package android.os;
 
+import static android.app.Flags.FLAG_PIC_CACHE_NULLS;
+import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import android.app.PropertyInvalidatedCache;
+import android.app.PropertyInvalidatedCache.Args;
 import android.multiuser.Flags;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.ravenwood.RavenwoodRule;
+import android.os.IpcDataCache;
 
 import androidx.test.filters.SmallTest;
 
@@ -43,6 +51,10 @@
 @SmallTest
 public class IpcDataCacheTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     // Configuration for creating caches
     private static final String MODULE = IpcDataCache.MODULE_TEST;
     private static final String API = "testApi";
@@ -287,7 +299,12 @@
         @Override
         public String apply(Integer qv) {
             mRecomputeCount += 1;
-            return "foo" + qv.toString();
+            // Special case for testing caches of nulls.  Integers in the range 30-40 return null.
+            if (qv >= 30 && qv < 40) {
+                return null;
+            } else {
+                return "foo" + qv.toString();
+            }
         }
 
         int getRecomputeCount() {
@@ -406,31 +423,16 @@
     }
 
     @Test
-    public void testConfig() {
+    public void testConfigDisable() {
+        // Create a set of caches based on a set of chained configs.
         IpcDataCache.Config a = new IpcDataCache.Config(8, MODULE, "apiA");
         TestCache ac = new TestCache(a);
-        assertEquals(8, a.maxEntries());
-        assertEquals(MODULE, a.module());
-        assertEquals("apiA", a.api());
-        assertEquals("apiA", a.name());
         IpcDataCache.Config b = new IpcDataCache.Config(a, "apiB");
         TestCache bc = new TestCache(b);
-        assertEquals(8, b.maxEntries());
-        assertEquals(MODULE, b.module());
-        assertEquals("apiB", b.api());
-        assertEquals("apiB", b.name());
         IpcDataCache.Config c = new IpcDataCache.Config(a, "apiC", "nameC");
         TestCache cc = new TestCache(c);
-        assertEquals(8, c.maxEntries());
-        assertEquals(MODULE, c.module());
-        assertEquals("apiC", c.api());
-        assertEquals("nameC", c.name());
         IpcDataCache.Config d = a.child("nameD");
         TestCache dc = new TestCache(d);
-        assertEquals(8, d.maxEntries());
-        assertEquals(MODULE, d.module());
-        assertEquals("apiA", d.api());
-        assertEquals("nameD", d.name());
 
         a.disableForCurrentProcess();
         assertEquals(ac.isDisabled(), true);
@@ -449,6 +451,7 @@
         assertEquals(ec.isDisabled(), true);
     }
 
+
     // Verify that invalidating the cache from an app process would fail due to lack of permissions.
     @Test
     @android.platform.test.annotations.DisabledOnRavenwood(
@@ -507,4 +510,47 @@
         // Re-enable test mode (so that the cleanup for the test does not throw).
         IpcDataCache.setTestMode(true);
     }
+
+    @RequiresFlagsEnabled(FLAG_PIC_CACHE_NULLS)
+    @Test
+    public void testCachingNulls() {
+        IpcDataCache.Config c =
+                new IpcDataCache.Config(4, IpcDataCache.MODULE_TEST, "testCachingNulls");
+        TestCache cache;
+        cache = new TestCache(c.cacheNulls(true));
+        cache.invalidateCache();
+        assertEquals("foo1", cache.query(1));
+        assertEquals("foo2", cache.query(2));
+        assertEquals(null, cache.query(30));
+        assertEquals(3, cache.getRecomputeCount());
+        assertEquals("foo1", cache.query(1));
+        assertEquals("foo2", cache.query(2));
+        assertEquals(null, cache.query(30));
+        assertEquals(3, cache.getRecomputeCount());
+
+        cache = new TestCache(c.cacheNulls(false));
+        cache.invalidateCache();
+        assertEquals("foo1", cache.query(1));
+        assertEquals("foo2", cache.query(2));
+        assertEquals(null, cache.query(30));
+        assertEquals(3, cache.getRecomputeCount());
+        assertEquals("foo1", cache.query(1));
+        assertEquals("foo2", cache.query(2));
+        assertEquals(null, cache.query(30));
+        // The recompute is 4 because nulls were not cached.
+        assertEquals(4, cache.getRecomputeCount());
+
+        // Verify that the default is not to cache nulls.
+        cache = new TestCache(c);
+        cache.invalidateCache();
+        assertEquals("foo1", cache.query(1));
+        assertEquals("foo2", cache.query(2));
+        assertEquals(null, cache.query(30));
+        assertEquals(3, cache.getRecomputeCount());
+        assertEquals("foo1", cache.query(1));
+        assertEquals("foo2", cache.query(2));
+        assertEquals(null, cache.query(30));
+        // The recompute is 4 because nulls were not cached.
+        assertEquals(4, cache.getRecomputeCount());
+    }
 }
diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
index 3b9f35b..e6586b3 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -496,4 +496,58 @@
             // expected
         }
     }
+
+    // Note: the zeroize() tests only test the behavior that can be tested from a Java test.
+    // They do not verify that no copy of the data is left anywhere.
+
+    @Test
+    @SmallTest
+    public void testZeroizeNonMovableByteArray() {
+        final int length = 10;
+        byte[] array = ArrayUtils.newNonMovableByteArray(length);
+        assertArrayEquals(array, new byte[length]);
+        Arrays.fill(array, (byte) 0xff);
+        ArrayUtils.zeroize(array);
+        assertArrayEquals(array, new byte[length]);
+    }
+
+    @Test
+    @SmallTest
+    public void testZeroizeRegularByteArray() {
+        final int length = 10;
+        byte[] array = new byte[length];
+        assertArrayEquals(array, new byte[length]);
+        Arrays.fill(array, (byte) 0xff);
+        ArrayUtils.zeroize(array);
+        assertArrayEquals(array, new byte[length]);
+    }
+
+    @Test
+    @SmallTest
+    public void testZeroizeNonMovableCharArray() {
+        final int length = 10;
+        char[] array = ArrayUtils.newNonMovableCharArray(length);
+        assertArrayEquals(array, new char[length]);
+        Arrays.fill(array, (char) 0xff);
+        ArrayUtils.zeroize(array);
+        assertArrayEquals(array, new char[length]);
+    }
+
+    @Test
+    @SmallTest
+    public void testZeroizeRegularCharArray() {
+        final int length = 10;
+        char[] array = new char[length];
+        assertArrayEquals(array, new char[length]);
+        Arrays.fill(array, (char) 0xff);
+        ArrayUtils.zeroize(array);
+        assertArrayEquals(array, new char[length]);
+    }
+
+    @Test
+    @SmallTest
+    public void testZeroize_acceptsNull() {
+        ArrayUtils.zeroize((byte[]) null);
+        ArrayUtils.zeroize((char[]) null);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS
new file mode 100644
index 0000000..84596b0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS
@@ -0,0 +1,6 @@
+# WM shell sub-module automotive owners
+
+winsonc@google.com
+stenning@google.com
+gauravbhola@google.com
+xiangw@google.com
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 38b8592..ec3c0b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -228,7 +228,6 @@
     public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener {
         final int mDisplayId;
         final InsetsState mInsetsState = new InsetsState();
-        @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
         boolean mImeRequestedVisible =
                 (WindowInsets.Type.defaultVisible() & WindowInsets.Type.ime()) != 0;
         InsetsSourceControl mImeSourceControl = null;
@@ -426,12 +425,10 @@
          */
         private void setVisibleDirectly(boolean visible, @Nullable ImeTracker.Token statsToken) {
             mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible);
-            mRequestedVisibleTypes = visible
-                    ? mRequestedVisibleTypes | WindowInsets.Type.ime()
-                    : mRequestedVisibleTypes & ~WindowInsets.Type.ime();
+            int visibleTypes = visible ? WindowInsets.Type.ime() : 0;
             try {
                 mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId,
-                        mRequestedVisibleTypes, statsToken);
+                        visibleTypes, WindowInsets.Type.ime(), statsToken);
             } catch (RemoteException e) {
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index 62d5098..bc56637 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -30,7 +30,7 @@
  * desktop windowing environment.
  */
 fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
-    (isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1))
+    (isSystemUiTask(context, task) || (task.numActivities > 0 && task.isActivityStackTransparent))
             && !task.isTopActivityNoDisplay
 
 private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt
index 819b110..2d0a3f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt
@@ -67,8 +67,8 @@
             return false
         }
         return when (args.size) {
-            1 -> onShellDisplayCommand(args[0], pw)
-            2 -> onShellUpdateCommand(args[0], args[1], pw)
+            1 -> onNoParamsCommand(args[0], pw)
+            2 -> onSingleParamCommand(args[0], args[1], pw)
             else -> {
                 pw.println("Invalid command: " + args[0])
                 return false
@@ -89,11 +89,17 @@
                     $prefix      name, for example, @android:color/system_accent2_50.
                     $prefix backgroundColorReset"
                     $prefix      Resets the background color to the default value."
+                    $prefix cornerRadius"
+                    $prefix      Corners radius (in pixels) for activities in the letterbox mode."
+                    $prefix      If cornerRadius < 0, it will be ignored and corners of the"
+                    $prefix      activity won't be rounded."
+                    $prefix cornerRadiusReset"
+                    $prefix      Resets the rounded corners radius to the default value."
                 """.trimIndent()
         )
     }
 
-    private fun onShellUpdateCommand(command: String, value: String, pw: PrintWriter): Boolean {
+    private fun onSingleParamCommand(command: String, value: String, pw: PrintWriter): Boolean {
         when (command) {
             "backgroundColor" -> {
                 return invokeWhenValid(
@@ -120,10 +126,17 @@
                 }
             )
 
-            "backgroundColorReset" -> {
-                letterboxConfiguration.resetLetterboxBackgroundColor()
-                return true
-            }
+            "cornerRadius" -> return invokeWhenValid(
+                pw,
+                value,
+                ::strToInt{ it >= 0 },
+                { radius ->
+                    letterboxConfiguration.setLetterboxActivityCornersRadius(radius)
+                },
+                { r ->
+                    "$r is not a valid radius. It must be an integer >= 0."
+                }
+            )
 
             else -> {
                 pw.println("Invalid command: $value")
@@ -132,7 +145,7 @@
         }
     }
 
-    private fun onShellDisplayCommand(command: String, pw: PrintWriter): Boolean {
+    private fun onNoParamsCommand(command: String, pw: PrintWriter): Boolean {
         when (command) {
             "backgroundColor" -> {
                 pw.println(
@@ -144,6 +157,24 @@
                 return true
             }
 
+            "backgroundColorReset" -> {
+                letterboxConfiguration.resetLetterboxBackgroundColor()
+                return true
+            }
+
+            "cornerRadius" -> {
+                pw.println(
+                    "    Rounded corners radius: " +
+                        "${letterboxConfiguration.getLetterboxActivityCornersRadius()} px."
+                )
+                return true
+            }
+
+            "cornerRadiusReset" -> {
+                letterboxConfiguration.resetLetterboxActivityCornersRadius()
+                return true
+            }
+
             else -> {
                 pw.println("Invalid command: $command")
                 return false
@@ -181,4 +212,15 @@
         } catch (e: IllegalArgumentException) {
             null
         }
+
+    // Converts a String to Int which if possible or it returns null otherwise.
+    // If a predicate is set, it also returns [null] if the predicate evaluate to [false].
+    private fun strToInt(predicate: (Int) -> Boolean = { _ -> true }): (String) -> Int? = { str ->
+        try {
+            val value = str.toInt()
+            if (predicate(value)) value else null
+        } catch (e: IllegalArgumentException) {
+            null
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
index 9e3edf6..0c3769e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.compatui.letterbox
 
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
 import com.android.wm.shell.dagger.WMSingleton
 import javax.inject.Inject
 
@@ -24,24 +26,28 @@
  * implementing letterbox in shell.
  */
 @WMSingleton
-class LetterboxControllerStrategy @Inject constructor() {
+class LetterboxControllerStrategy @Inject constructor(
+    private val letterboxConfiguration: LetterboxConfiguration
+) {
 
     // Different letterbox implementation modes.
     enum class LetterboxMode { SINGLE_SURFACE, MULTIPLE_SURFACES }
 
     @Volatile
-    private var currentMode: LetterboxMode = LetterboxMode.SINGLE_SURFACE
+    private var currentMode: LetterboxMode = SINGLE_SURFACE
 
     fun configureLetterboxMode() {
         // TODO(b/377875146): Define criteria for switching between [LetterboxMode]s.
-        currentMode = if (android.os.SystemProperties.getInt(
-                "multi_interface",
-                0
-            ) == 0
-        ) {
-            LetterboxMode.SINGLE_SURFACE
+        // At the moment, we use the presence of rounded corners to understand if to use a single
+        // surface or multiple surfaces for the letterbox areas. This rule will change when
+        // considering transparent activities which won't have rounded corners leading to the
+        // [MULTIPLE_SURFACES] option.
+        // The chosen strategy will depend on performance considerations,
+        // including surface memory usage and the impact of the rounded corners solution.
+        currentMode = if (letterboxConfiguration.isLetterboxActivityCornersRounded()) {
+            SINGLE_SURFACE
         } else {
-            LetterboxMode.MULTIPLE_SURFACES
+            MULTIPLE_SURFACES
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt
new file mode 100644
index 0000000..d5e0240
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger
+
+/**
+ * An interface implemented by the application that uses [WMComponent].
+ *
+ * This exposes the component to allow classes to do member injection for bindings where constructor
+ * injection is not possible, e.g. views.
+ */
+interface HasWMComponent {
+    fun getWMComponent(): WMComponent
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
index a3cdb2e..c493aad 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
@@ -14,17 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dagger;
+package com.android.wm.shell.dagger;
 
 import android.os.HandlerThread;
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.SystemUIInitializer;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.dagger.WMShellModule;
-import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
@@ -45,12 +42,11 @@
 
 /**
  * Dagger Subcomponent for WindowManager.  This class explicitly describes the interfaces exported
- * from the WM component into the SysUI component (in
- * {@link SystemUIInitializer#init(boolean)}), and references the specific dependencies
+ * from the WM component into the SysUI component, and references the specific dependencies
  * provided by its particular device/form-factor SystemUI implementation.
  *
- * ie. {@link WMComponent} includes {@link WMShellModule}
- * and {@code TvWMComponent} includes {@link com.android.wm.shell.dagger.TvWMShellModule}
+ * <p> ie. {@link WMComponent} includes {@link WMShellModule} and {@code TvWMComponent} includes
+ * {@link TvWMShellModule}
  */
 @WMSingleton
 @Subcomponent(modules = {WMShellModule.class})
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 19428ee..e309da1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -99,7 +99,7 @@
     private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
 
     // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
-    private PipAnimationListener mPipRecentsAnimationListener;
+    @Nullable private PipAnimationListener mPipRecentsAnimationListener;
 
     @VisibleForTesting
     interface PipAnimationListener {
@@ -378,7 +378,9 @@
             tx.setLayer(overlay, Integer.MAX_VALUE);
             tx.apply();
         }
-        mPipRecentsAnimationListener.onPipAnimationStarted();
+        if (mPipRecentsAnimationListener != null) {
+            mPipRecentsAnimationListener.onPipAnimationStarted();
+        }
     }
 
     private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 44cc563..fc3fbe2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -222,7 +222,10 @@
                 pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger,
                 menuController, mainExecutor,
                 mPipPerfHintController);
-        mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
+        mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> {
+            updateMinMaxSize(aspectRatio);
+            onAspectRatioChanged();
+        });
 
         mMoveOnShelVisibilityChanged = () -> {
             if (mIsImeShowing && mImeHeight > mShelfHeight) {
@@ -768,18 +771,19 @@
     private void animateToNormalSize(Runnable callback) {
         // Save the current bounds as the user-resize bounds.
         mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
-
-        final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
-        final Size defaultSize = mSizeSpecSource.getDefaultSize(mPipBoundsState.getAspectRatio());
-        final Rect normalBounds = new Rect(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
-        final Rect adjustedNormalBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(
-                normalBounds, minMenuSize);
-
+        final Rect adjustedNormalBounds = getAdjustedNormalBounds();
         mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds,
                 getMovementBounds(mPipBoundsState.getBounds()),
                 getMovementBounds(adjustedNormalBounds), callback /* callback */);
     }
 
+    private Rect getAdjustedNormalBounds() {
+        final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
+        final Size defaultSize = mSizeSpecSource.getDefaultSize(mPipBoundsState.getAspectRatio());
+        final Rect normalBounds = new Rect(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
+        return mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize);
+    }
+
     private void animateToUnexpandedState(Rect restoreBounds) {
         mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
                 getMovementBounds(restoreBounds),
@@ -1065,6 +1069,10 @@
         mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
                 insetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
         mMotionHelper.onMovementBoundsChanged();
+
+        if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
+            mPipResizeGestureHandler.setUserResizeBounds(getAdjustedNormalBounds());
+        }
     }
 
     private Rect getMovementBounds(Rect curBounds) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 1a012e0..1efe2ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -196,7 +196,7 @@
             @NonNull TransitionRequestInfo request) {
         if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
             mEnterTransition = transition;
-            return getEnterPipTransaction(transition, request);
+            return getEnterPipTransaction(transition, request.getPipChange());
         }
         return null;
     }
@@ -205,7 +205,8 @@
     public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
             @NonNull WindowContainerTransaction outWct) {
         if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
-            outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
+            outWct.merge(getEnterPipTransaction(transition, request.getPipChange()),
+                    true /* transfer */);
             mEnterTransition = transition;
         }
     }
@@ -775,9 +776,9 @@
     }
 
     private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
-            @NonNull TransitionRequestInfo request) {
+            @NonNull TransitionRequestInfo.PipChange pipChange) {
         // cache the original task token to check for multi-activity case later
-        final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
+        final ActivityManager.RunningTaskInfo pipTask = pipChange.getTaskInfo();
         PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
         mPipTaskListener.setPictureInPictureParams(pipParams);
         mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
@@ -787,14 +788,18 @@
         final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         mPipBoundsState.setBounds(entryBounds);
 
+        // Operate on the TF token in case we are dealing with AE case; this should avoid marking
+        // activities in other TFs as config-at-end.
+        WindowContainerToken token = pipChange.getTaskFragmentToken();
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
-        wct.deferConfigToTransitionEnd(pipTask.token);
+        wct.movePipActivityToPinnedRootTask(token, entryBounds);
+        wct.deferConfigToTransitionEnd(token);
         return wct;
     }
 
     private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
-        final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
+        final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipChange() != null
+                ? requestInfo.getPipChange().getTaskInfo() : null;
         if (pipTask == null) {
             return false;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index b922cd0..0869caa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -550,7 +550,9 @@
                 groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
                         mTaskSplitBoundsMap.get(pairedTaskId)));
             } else {
-                if (Flags.enableRefactorTaskThumbnail() && isWallpaperTask(taskInfo)) {
+                if (
+                        Flags.enableUseTopVisibleActivityForExcludeFromRecentTask()
+                                && isWallpaperTask(taskInfo)) {
                     // Don't add the wallpaper task as an entry in grouped tasks
                     continue;
                 }
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 9dbac76..85e3068 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
@@ -588,8 +588,16 @@
 
     private void openHandleMenu(int taskId) {
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
-        decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo)
-                >= MANAGE_WINDOWS_MINIMUM_INSTANCES);
+        // TODO(b/379873022): Run the instance check and the AssistContent request in
+        //  createHandleMenu on the same bg thread dispatch.
+        mBgExecutor.execute(() -> {
+            final int numOfInstances = checkNumberOfOtherInstances(decoration.mTaskInfo);
+            mMainExecutor.execute(() -> {
+                decoration.createHandleMenu(
+                        numOfInstances >= MANAGE_WINDOWS_MINIMUM_INSTANCES
+                );
+            });
+        });
     }
 
     private void onToggleSizeInteraction(
@@ -763,12 +771,20 @@
             return;
         }
         decoration.closeHandleMenu();
-        decoration.createManageWindowsMenu(getTaskSnapshots(decoration.mTaskInfo),
-                /* onIconClickListener= */(Integer requestedTaskId) -> {
-                    decoration.closeManageWindowsMenu();
-                    mDesktopTasksController.openInstance(decoration.mTaskInfo, requestedTaskId);
-                    return Unit.INSTANCE;
-                });
+        mBgExecutor.execute(() -> {
+            final ArrayList<Pair<Integer, TaskSnapshot>> snapshotList =
+                    getTaskSnapshots(decoration.mTaskInfo);
+            mMainExecutor.execute(() -> decoration.createManageWindowsMenu(
+                    snapshotList,
+                    /* onIconClickListener= */ (Integer requestedTaskId) -> {
+                        decoration.closeManageWindowsMenu();
+                        mDesktopTasksController.openInstance(decoration.mTaskInfo,
+                                requestedTaskId);
+                        return Unit.INSTANCE;
+                    }
+                )
+            );
+        });
     }
 
     private ArrayList<Pair<Integer, TaskSnapshot>> getTaskSnapshots(
@@ -1814,11 +1830,10 @@
         // TODO(b/336289597): Rather than returning number of instances, return a list of valid
         //  instances, then refer to the list's size and reuse the list for Manage Windows menu.
         final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService();
-        final IActivityManager activityManager = ActivityManager.getService();
         try {
             return activityTaskManager.getRecentTasks(Integer.MAX_VALUE,
                     ActivityManager.RECENT_WITH_EXCLUDED,
-                    activityManager.getCurrentUserId()).getList().stream().filter(
+                    info.userId).getList().stream().filter(
                             recentTaskInfo -> (recentTaskInfo.taskId != info.taskId
                                     && recentTaskInfo.baseActivity != null
                                     && recentTaskInfo.baseActivity.getPackageName()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
index 50aa21e..a205ac6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
@@ -20,10 +20,8 @@
 import android.graphics.Region
 import android.view.Display
 import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
 import android.view.View
 import android.view.WindowManager
-import android.view.WindowlessWindowManager
 import androidx.tracing.Trace
 import com.android.internal.annotations.VisibleForTesting
 import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -31,41 +29,23 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 
-typealias SurfaceControlViewHostFactory =
-    (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
-
 /**
- * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost].
+ * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHostAdapter].
  *
- * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
- * any attempts to do will throw, which means that once a [View] is added using [updateView] or
- * [updateViewAsync], only its properties and binding may be changed, its children views may be
- * added, removed or changed and its [WindowManager.LayoutParams] may be changed. It also supports
- * asynchronously updating the view hierarchy using [updateViewAsync], in which case the update work
- * will be posted on the [ShellMainThread] with no delay.
+ * It supports asynchronously updating the view hierarchy using [updateViewAsync], in which
+ * case the update work will be posted on the [ShellMainThread] with no delay.
  */
 class DefaultWindowDecorViewHost(
-    private val context: Context,
+    context: Context,
     @ShellMainThread private val mainScope: CoroutineScope,
-    private val display: Display,
-    private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
-        SurfaceControlViewHost(c, d, wwm, s)
-    },
+    display: Display,
+    @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter =
+        SurfaceControlViewHostAdapter(context, display),
 ) : WindowDecorViewHost {
-
-    private val rootSurface: SurfaceControl =
-        SurfaceControl.Builder()
-            .setName("DefaultWindowDecorViewHost surface")
-            .setContainerLayer()
-            .setCallsite("DefaultWindowDecorViewHost#init")
-            .build()
-
-    private var wwm: WindowDecorWindowlessWindowManager? = null
-    @VisibleForTesting var viewHost: SurfaceControlViewHost? = null
     private var currentUpdateJob: Job? = null
 
     override val surfaceControl: SurfaceControl
-        get() = rootSurface
+        get() = viewHostAdapter.rootSurface
 
     override fun updateView(
         view: View,
@@ -103,8 +83,7 @@
 
     override fun release(t: SurfaceControl.Transaction) {
         clearCurrentUpdateJob()
-        viewHost?.release()
-        t.remove(rootSurface)
+        viewHostAdapter.release(t)
     }
 
     private fun updateViewHost(
@@ -115,33 +94,11 @@
         onDrawTransaction: SurfaceControl.Transaction?,
     ) {
         Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost")
-        if (wwm == null) {
-            wwm = WindowDecorWindowlessWindowManager(configuration, rootSurface)
+        viewHostAdapter.prepareViewHost(configuration, touchableRegion)
+        onDrawTransaction?.let {
+            viewHostAdapter.applyTransactionOnDraw(it)
         }
-        if (viewHost == null) {
-            viewHost =
-                surfaceControlViewHostFactory.invoke(
-                    context,
-                    display,
-                    requireWindowlessWindowManager(),
-                    "DefaultWindowDecorViewHost#updateViewHost",
-                )
-        }
-        requireWindowlessWindowManager().apply {
-            setConfiguration(configuration)
-            setTouchRegion(requireViewHost(), touchableRegion)
-        }
-        onDrawTransaction?.let { requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it) }
-        if (requireViewHost().view == null) {
-            Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView")
-            requireViewHost().setView(view, attrs)
-            Trace.endSection()
-        } else {
-            check(requireViewHost().view == view) { "Changing view is not allowed" }
-            Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-relayout")
-            requireViewHost().relayout(attrs)
-            Trace.endSection()
-        }
+        viewHostAdapter.updateView(view, attrs)
         Trace.endSection()
     }
 
@@ -149,12 +106,4 @@
         currentUpdateJob?.cancel()
         currentUpdateJob = null
     }
-
-    private fun requireWindowlessWindowManager(): WindowDecorWindowlessWindowManager {
-        return wwm ?: error("Expected non-null windowless window manager")
-    }
-
-    private fun requireViewHost(): SurfaceControlViewHost {
-        return viewHost ?: error("Expected non-null view host")
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt
new file mode 100644
index 0000000..26a43f4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor.common.viewhost
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Region
+import android.view.AttachedSurfaceControl
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import androidx.tracing.Trace
+import com.android.internal.annotations.VisibleForTesting
+
+typealias SurfaceControlViewHostFactory =
+    (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
+
+/**
+ * Adapter for a [SurfaceControlViewHost] and its backing [SurfaceControl].
+ *
+ * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
+ * any attempts to do will throw, which means that once a [View] is added using [updateView], only
+ * its properties and binding may be changed, children views may be added, removed or changed
+ * and its [WindowManager.LayoutParams] may be changed.
+ */
+class SurfaceControlViewHostAdapter(
+    private val context: Context,
+    private val display: Display,
+    private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
+        SurfaceControlViewHost(c, d, wwm, s)
+    },
+) {
+    val rootSurface: SurfaceControl =
+        SurfaceControl.Builder()
+            .setName("SurfaceControlViewHostAdapter surface")
+            .setContainerLayer()
+            .setCallsite("SurfaceControlViewHostAdapter#init")
+            .build()
+
+    private var wwm: WindowDecorWindowlessWindowManager? = null
+    @VisibleForTesting var viewHost: SurfaceControlViewHost? = null
+
+    /**
+     * Initialize or updates the [SurfaceControlViewHost].
+     */
+    fun prepareViewHost(
+        configuration: Configuration,
+        touchableRegion: Region?
+    ) {
+        if (wwm == null) {
+            wwm = WindowDecorWindowlessWindowManager(configuration, rootSurface)
+        }
+        if (viewHost == null) {
+            viewHost =
+                surfaceControlViewHostFactory.invoke(
+                    context,
+                    display,
+                    requireWindowlessWindowManager(),
+                    "SurfaceControlViewHostAdapter#prepareViewHost",
+                )
+        }
+        requireWindowlessWindowManager().setConfiguration(configuration)
+        requireWindowlessWindowManager().setTouchRegion(requireViewHost(), touchableRegion)
+    }
+
+    /**
+     * Request to apply the transaction atomically with the next draw of the view hierarchy. See
+     * [AttachedSurfaceControl.applyTransactionOnDraw].
+     */
+    fun applyTransactionOnDraw(t: SurfaceControl.Transaction) {
+        requireViewHost().rootSurfaceControl.applyTransactionOnDraw(t)
+    }
+
+    /** Update the view hierarchy of the view host. */
+    fun updateView(view: View, attrs: WindowManager.LayoutParams) {
+        if (requireViewHost().view == null) {
+            Trace.beginSection("SurfaceControlViewHostAdapter#updateView-setView")
+            requireViewHost().setView(view, attrs)
+            Trace.endSection()
+        } else {
+            check(requireViewHost().view == view) { "Changing view is not allowed" }
+            Trace.beginSection("SurfaceControlViewHostAdapter#updateView-relayout")
+            requireViewHost().relayout(attrs)
+            Trace.endSection()
+        }
+    }
+
+    /** Release the view host and remove the backing surface. */
+    fun release(t: SurfaceControl.Transaction) {
+        viewHost?.release()
+        t.remove(rootSurface)
+    }
+
+    /** Whether the view host has had a view hierarchy set. */
+    fun isInitialized(): Boolean = viewHost?.view != null
+
+    private fun requireWindowlessWindowManager(): WindowDecorWindowlessWindowManager {
+        return wwm ?: error("Expected non-null windowless window manager")
+    }
+
+    private fun requireViewHost(): SurfaceControlViewHost {
+        return viewHost ?: error("Expected non-null view host")
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 310c2d7..ec3fe95 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -54,6 +54,7 @@
     private final Point mPositionInParent = new Point();
     private boolean mIsVisible = false;
     private boolean mIsTopActivityTransparent = false;
+    private boolean mIsActivityStackTransparent = false;
     private int mNumActivities = 1;
     private long mLastActiveTime;
 
@@ -158,6 +159,12 @@
         return this;
     }
 
+    public TestRunningTaskInfoBuilder setActivityStackTransparent(
+            boolean isActivityStackTransparent) {
+        mIsActivityStackTransparent = isActivityStackTransparent;
+        return this;
+    }
+
     public TestRunningTaskInfoBuilder setNumActivities(int numActivities) {
         mNumActivities = numActivities;
         return this;
@@ -187,6 +194,7 @@
         info.positionInParent = mPositionInParent;
         info.isVisible = mIsVisible;
         info.isTopActivityTransparent = mIsTopActivityTransparent;
+        info.isActivityStackTransparent = mIsActivityStackTransparent;
         info.numActivities = mNumActivities;
         info.lastActiveTime = mLastActiveTime;
         info.userId = mUserId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index 1d39000..d52fd4f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -37,35 +37,46 @@
 @SmallTest
 class AppCompatUtilsTest : ShellTestCase() {
     @Test
-    fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent() {
+    fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() {
         assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
             createFreeformTask(/* displayId */ 0)
                     .apply {
-                        isTopActivityTransparent = true
-                        numActivities = 1
+                        isActivityStackTransparent = true
                         isTopActivityNoDisplay = false
+                        numActivities = 1
                     }))
     }
 
     @Test
-    fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_multipleActivities() {
+    fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() {
         assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
             createFreeformTask(/* displayId */ 0)
                 .apply {
-                    isTopActivityTransparent = true
-                    numActivities = 2
+                    isActivityStackTransparent = true
                     isTopActivityNoDisplay = false
+                    numActivities = 0
                 }))
     }
 
     @Test
-    fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_notDisplayed() {
+    fun testIsTopActivityExemptFromDesktopWindowing_nonTransparentActivitiesInStack() {
         assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
             createFreeformTask(/* displayId */ 0)
                 .apply {
-                    isTopActivityTransparent = true
+                    isActivityStackTransparent = false
+                    isTopActivityNoDisplay = false
                     numActivities = 1
+                }))
+    }
+
+    @Test
+    fun testIsTopActivityExemptFromDesktopWindowing_transparentActivityStack_notDisplayed() {
+        assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+            createFreeformTask(/* displayId */ 0)
+                .apply {
+                    isActivityStackTransparent = true
                     isTopActivityNoDisplay = true
+                    numActivities = 1
                 }))
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt
new file mode 100644
index 0000000..50fdf45
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [LetterboxControllerStrategy].
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:LetterboxControllerStrategyTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxControllerStrategyTest : ShellTestCase() {
+
+    @Test
+    fun `LetterboxMode is MULTIPLE_SURFACES with rounded corners`() {
+        runTestScenario { r ->
+            r.configureRoundedCornerRadius(true)
+            r.configureLetterboxMode()
+            r.checkLetterboxModeIsSingle()
+        }
+    }
+
+    @Test
+    fun `LetterboxMode is MULTIPLE_SURFACES with no rounded corners`() {
+        runTestScenario { r ->
+            r.configureRoundedCornerRadius(false)
+            r.configureLetterboxMode()
+            r.checkLetterboxModeIsMultiple()
+        }
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    fun runTestScenario(consumer: Consumer<LetterboxStrategyRobotTest>) {
+        val robot = LetterboxStrategyRobotTest(mContext)
+        consumer.accept(robot)
+    }
+
+    class LetterboxStrategyRobotTest(val ctx: Context) {
+
+        companion object {
+            @JvmStatic
+            private val ROUNDED_CORNERS_TRUE = 10
+            @JvmStatic
+            private val ROUNDED_CORNERS_FALSE = 0
+        }
+
+        private val letterboxConfiguration: LetterboxConfiguration
+        private val letterboxStrategy: LetterboxControllerStrategy
+
+        init {
+            letterboxConfiguration = LetterboxConfiguration(ctx)
+            letterboxStrategy = LetterboxControllerStrategy(letterboxConfiguration)
+        }
+
+        fun configureRoundedCornerRadius(enabled: Boolean) {
+            letterboxConfiguration.setLetterboxActivityCornersRadius(
+                if (enabled) ROUNDED_CORNERS_TRUE else ROUNDED_CORNERS_FALSE
+            )
+        }
+
+        fun configureLetterboxMode() {
+            letterboxStrategy.configureLetterboxMode()
+        }
+
+        fun checkLetterboxModeIsSingle(expected: Boolean = true) {
+            val expectedMode = if (expected) SINGLE_SURFACE else MULTIPLE_SURFACES
+            assertEquals(expectedMode, letterboxStrategy.getLetterboxImplementationMode())
+        }
+
+        fun checkLetterboxModeIsMultiple(expected: Boolean = true) {
+            val expectedMode = if (expected) MULTIPLE_SURFACES else SINGLE_SURFACE
+            assertEquals(expectedMode, letterboxStrategy.getLetterboxImplementationMode())
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 8e21071..3bee588 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1151,7 +1151,7 @@
   fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
     val task =
       setUpFullscreenTask().apply {
-        isTopActivityTransparent = true
+        isActivityStackTransparent = true
         isTopActivityNoDisplay = true
         numActivities = 1
       }
@@ -1167,7 +1167,7 @@
   fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
     val task =
       setUpFullscreenTask().apply {
-        isTopActivityTransparent = true
+        isActivityStackTransparent = true
         isTopActivityNoDisplay = false
         numActivities = 1
       }
@@ -2260,7 +2260,7 @@
 
     val task =
       setUpFullscreenTask().apply {
-        isTopActivityTransparent = true
+        isActivityStackTransparent = true
         isTopActivityNoDisplay = true
         numActivities = 1
       }
@@ -2278,7 +2278,7 @@
 
     val task =
       setUpFreeformTask().apply {
-        isTopActivityTransparent = true
+        isActivityStackTransparent = true
         isTopActivityNoDisplay = false
         numActivities = 1
       }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index 866d1b3..aee8821 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -73,10 +73,10 @@
             .setLastActiveTime(100)
             .build()
 
-    /** Create a new System Modal task, i.e. a task with a single transparent activity. */
+    /** Create a new System Modal task, i.e. a task with only transparent activities. */
     fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
         createFullscreenTaskBuilder(displayId)
-            .setTopActivityTransparent(true)
+            .setActivityStackTransparent(true)
             .setNumActivities(1)
             .build()
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index b9d7bbf..c33005e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -43,6 +43,10 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
+/**
+ * Tests for {@link SystemModalsTransitionHandler}
+ * Usage: atest WMShellUnitTests:SystemModalsTransitionHandlerTest
+ */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class SystemModalsTransitionHandlerTest : ShellTestCase() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index c6835b7..22b45e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -22,7 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.launcher3.Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL;
+import static com.android.launcher3.Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK;
 import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 
@@ -250,7 +250,7 @@
                 t3.taskId, -1);
     }
 
-    @EnableFlags(FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+    @EnableFlags(FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
     @Test
     public void testGetRecentTasks_removesDesktopWallpaperActivity() {
         RecentTaskInfo t1 = makeTaskInfo(1);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
index a15b611..8b4cf6d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
@@ -125,6 +125,8 @@
             times(1)
         ).setAppHandleEducationTooltipCallbacks(openHandleMenuCallbackCaptor.capture(), any())
         openHandleMenuCallbackCaptor.lastValue.invoke(task.taskId)
+        bgExecutor.flushAll()
+        testShellExecutor.flushAll()
 
         verify(decor, times(1)).createHandleMenu(anyBoolean())
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index a4e3af4..88f62d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -278,7 +278,7 @@
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
     fun testDecorationIsNotCreatedForTopTranslucentActivities() {
         val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
-            isTopActivityTransparent = true
+            isActivityStackTransparent = true
             isTopActivityNoDisplay = false
             numActivities = 1
         }
@@ -780,6 +780,8 @@
             times(1)
         ).setAppHandleEducationTooltipCallbacks(openHandleMenuCallbackCaptor.capture(), any())
         openHandleMenuCallbackCaptor.lastValue.invoke(task.taskId)
+        bgExecutor.flushAll()
+        testShellExecutor.flushAll()
 
         verify(decor, times(1)).createHandleMenu(anyBoolean())
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index dce44b7..6be234e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -286,6 +286,7 @@
                 } else {
                     statusBars()
                 }
+                userId = context.userId
             }
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt
index 2f223de..4f19f34 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt
@@ -18,7 +18,6 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
 import android.view.View
 import android.view.WindowManager
 import androidx.test.filters.SmallTest
@@ -28,7 +27,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
@@ -57,54 +55,8 @@
             onDrawTransaction = null,
         )
 
-        assertThat(windowDecorViewHost.viewHost).isNotNull()
-        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
-    }
-
-    @Test
-    fun updateView_alreadyLaidOut_relayouts() = runTest {
-        val windowDecorViewHost = createDefaultViewHost()
-        val view = View(context)
-        windowDecorViewHost.updateView(
-            view = view,
-            attrs = WindowManager.LayoutParams(100, 100),
-            configuration = context.resources.configuration,
-            onDrawTransaction = null,
-        )
-
-        val otherParams = WindowManager.LayoutParams(200, 200)
-        windowDecorViewHost.updateView(
-            view = view,
-            attrs = otherParams,
-            configuration = context.resources.configuration,
-            onDrawTransaction = null,
-        )
-
-        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
-        assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
-            .isEqualTo(otherParams.width)
-    }
-
-    @Test
-    fun updateView_replacingView_throws() = runTest {
-        val windowDecorViewHost = createDefaultViewHost()
-        val view = View(context)
-        windowDecorViewHost.updateView(
-            view = view,
-            attrs = WindowManager.LayoutParams(100, 100),
-            configuration = context.resources.configuration,
-            onDrawTransaction = null,
-        )
-
-        val otherView = View(context)
-        assertThrows(Exception::class.java) {
-            windowDecorViewHost.updateView(
-                view = otherView,
-                attrs = WindowManager.LayoutParams(100, 100),
-                configuration = context.resources.configuration,
-                onDrawTransaction = null,
-            )
-        }
+        assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+        assertThat(windowDecorViewHost.view()).isEqualTo(view)
     }
 
     @OptIn(ExperimentalCoroutinesApi::class)
@@ -123,7 +75,7 @@
         )
 
         // No view host yet, since the coroutine hasn't run.
-        assertThat(windowDecorViewHost.viewHost).isNull()
+        assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse()
 
         windowDecorViewHost.updateView(
             view = syncView,
@@ -135,14 +87,13 @@
         // Would run coroutine if it hadn't been cancelled.
         advanceUntilIdle()
 
-        assertThat(windowDecorViewHost.viewHost).isNotNull()
-        assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
+        assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+        assertThat(windowDecorViewHost.view()).isNotNull()
         // View host view/attrs should match the ones from the sync call, plus, since the
         // sync/async were made with different views, if the job hadn't been cancelled there
         // would've been an exception thrown as replacing views isn't allowed.
-        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView)
-        assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
-            .isEqualTo(syncAttrs.width)
+        assertThat(windowDecorViewHost.view()).isEqualTo(syncView)
+        assertThat(windowDecorViewHost.view()!!.layoutParams.width).isEqualTo(syncAttrs.width)
     }
 
     @OptIn(ExperimentalCoroutinesApi::class)
@@ -158,11 +109,11 @@
             configuration = context.resources.configuration,
         )
 
-        assertThat(windowDecorViewHost.viewHost).isNull()
+        assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse()
 
         advanceUntilIdle()
 
-        assertThat(windowDecorViewHost.viewHost).isNotNull()
+        assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
     }
 
     @OptIn(ExperimentalCoroutinesApi::class)
@@ -185,9 +136,8 @@
 
         advanceUntilIdle()
 
-        assertThat(windowDecorViewHost.viewHost).isNotNull()
-        assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
-        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView)
+        assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+        assertThat(windowDecorViewHost.view()).isEqualTo(otherView)
     }
 
     @Test
@@ -205,8 +155,7 @@
         val t = mock(SurfaceControl.Transaction::class.java)
         windowDecorViewHost.release(t)
 
-        verify(windowDecorViewHost.viewHost!!).release()
-        verify(t).remove(windowDecorViewHost.surfaceControl)
+        verify(windowDecorViewHost.viewHostAdapter).release(t)
     }
 
     private fun CoroutineScope.createDefaultViewHost() =
@@ -214,8 +163,8 @@
             context = context,
             mainScope = this,
             display = context.display,
-            surfaceControlViewHostFactory = { c, d, wwm, s ->
-                spy(SurfaceControlViewHost(c, d, wwm, s))
-            },
+            viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)),
         )
+
+    private fun DefaultWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt
new file mode 100644
index 0000000..5109a7c3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor.common.viewhost
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [SurfaceControlViewHostAdapter].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:SurfaceControlViewHostAdapterTest
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class SurfaceControlViewHostAdapterTest : ShellTestCase() {
+
+    private lateinit var adapter: SurfaceControlViewHostAdapter
+
+    @Before
+    fun setUp() {
+        adapter = SurfaceControlViewHostAdapter(
+            context,
+            context.display,
+            surfaceControlViewHostFactory = { c, d, wwm, s ->
+                spy(SurfaceControlViewHost(c, d, wwm, s))
+            }
+        )
+    }
+
+    @Test
+    fun prepareViewHost() {
+        adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+        assertThat(adapter.viewHost).isNotNull()
+    }
+
+    @Test
+    fun prepareViewHost_alreadyCreated_skips() {
+        adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+        val viewHost = adapter.viewHost!!
+
+        adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+        assertThat(adapter.viewHost).isEqualTo(viewHost)
+    }
+
+    @Test
+    fun updateView_layoutInViewHost() {
+        val view = View(context)
+        adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+        adapter.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100)
+        )
+
+        assertThat(adapter.isInitialized()).isTrue()
+        assertThat(adapter.view()).isEqualTo(view)
+    }
+
+    @Test
+    fun updateView_alreadyLaidOut_relayouts() {
+        val view = View(context)
+        adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+        adapter.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100)
+        )
+
+        val otherParams = WindowManager.LayoutParams(200, 200)
+        adapter.updateView(
+            view = view,
+            attrs = otherParams
+        )
+
+        assertThat(adapter.view()).isEqualTo(view)
+        assertThat(adapter.view()!!.layoutParams.width).isEqualTo(otherParams.width)
+    }
+
+    @Test
+    fun updateView_replacingView_throws() {
+        val view = View(context)
+        adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+        adapter.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100)
+        )
+
+        val otherView = View(context)
+        assertThrows(Exception::class.java) {
+            adapter.updateView(
+                view = otherView,
+                attrs = WindowManager.LayoutParams(100, 100)
+            )
+        }
+    }
+
+    @Test
+    fun release() {
+        adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+        adapter.updateView(
+            view = View(context),
+            attrs = WindowManager.LayoutParams(100, 100)
+        )
+
+        val mockT = mock(SurfaceControl.Transaction::class.java)
+        adapter.release(mockT)
+
+        verify(adapter.viewHost!!).release()
+        verify(mockT).remove(adapter.rootSurface)
+    }
+
+    private fun SurfaceControlViewHostAdapter.view(): View? = viewHost?.view
+}
\ No newline at end of file
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 5e645cc..a592749 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -38,7 +38,7 @@
 }
 
 CursorWindow::~CursorWindow() {
-    if (mAshmemFd != -1) {
+    if (mAshmemFd >= 0) {
         ::munmap(mData, mSize);
         ::close(mAshmemFd);
     } else {
@@ -155,23 +155,27 @@
     bool isAshmem;
     if (parcel->readBool(&isAshmem)) goto fail;
     if (isAshmem) {
-        window->mAshmemFd = parcel->readFileDescriptor();
-        if (window->mAshmemFd < 0) {
+        int tempFd = parcel->readFileDescriptor();
+        if (tempFd < 0) {
             LOG(ERROR) << "Failed readFileDescriptor";
             goto fail_silent;
         }
 
-        window->mAshmemFd = ::fcntl(window->mAshmemFd, F_DUPFD_CLOEXEC, 0);
-        if (window->mAshmemFd < 0) {
+        tempFd = ::fcntl(tempFd, F_DUPFD_CLOEXEC, 0);
+        if (tempFd < 0) {
             PLOG(ERROR) << "Failed F_DUPFD_CLOEXEC";
             goto fail_silent;
         }
 
-        window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, window->mAshmemFd, 0);
+        window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, tempFd, 0);
         if (window->mData == MAP_FAILED) {
+            ::close(tempFd);
             PLOG(ERROR) << "Failed mmap";
             goto fail_silent;
         }
+
+        window->mAshmemFd = tempFd;
+
     } else {
         window->mAshmemFd = -1;
 
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index de40209..139ccfd 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -16,6 +16,7 @@
     field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
     field public static final int ERROR_DENIED = 1000; // 0x3e8
     field public static final int ERROR_DISABLED = 1002; // 0x3ea
+    field public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; // 0x7d2
     field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
     field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
     field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
index 2540236..0c52169 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
@@ -71,6 +71,13 @@
     public static final int ERROR_CANCELLED = 2001;
 
     /**
+     * The operation was disallowed by enterprise policy.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+     */
+    public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002;
+
+    /**
      * An unknown error occurred while processing the call in the AppFunctionService.
      *
      * <p>This error is thrown when the service is connected in the remote application but an
@@ -189,7 +196,8 @@
                 ERROR_SYSTEM_ERROR,
                 ERROR_INVALID_ARGUMENT,
                 ERROR_DISABLED,
-                ERROR_CANCELLED
+                ERROR_CANCELLED,
+                ERROR_ENTERPRISE_POLICY_DISALLOWED
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ErrorCode {}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 54f172c..a3ad340 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -49,6 +49,7 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Describes the properties of a route.
@@ -640,7 +641,7 @@
     private final String mProviderId;
     private final boolean mIsVisibilityRestricted;
     private final Set<String> mAllowedPackages;
-    private final Set<String> mRequiredPermissions;
+    private final List<Set<String>> mRequiredPermissions;
     @SuitabilityStatus private final int mSuitabilityStatus;
 
     MediaRoute2Info(@NonNull Builder builder) {
@@ -665,7 +666,7 @@
         mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
         mAllowedPackages = builder.mAllowedPackages;
         mSuitabilityStatus = builder.mSuitabilityStatus;
-        mRequiredPermissions = Set.copyOf(builder.mRequiredPermissions);
+        mRequiredPermissions = List.copyOf(builder.mRequiredPermissions);
     }
 
     MediaRoute2Info(@NonNull Parcel in) {
@@ -690,7 +691,12 @@
         mProviderId = in.readString();
         mIsVisibilityRestricted = in.readBoolean();
         mAllowedPackages = Set.of(in.createString8Array());
-        mRequiredPermissions = Set.of(in.createString8Array());
+        ArrayList<Set<String>> requiredPermissions = new ArrayList<>();
+        int numRequiredPermissionSets = in.readInt();
+        for (int i = 0; i < numRequiredPermissionSets; i++) {
+            requiredPermissions.add(Set.of(in.createString8Array()));
+        }
+        mRequiredPermissions = List.copyOf(requiredPermissions); // Use copyOf to make it immutable.
         mSuitabilityStatus = in.readInt();
     }
 
@@ -934,11 +940,12 @@
     }
 
     /**
-     * @return the set of permissions which must be held to see this route
+     * @return a list of permission sets - all the permissions in at least one of these sets must be
+     * held to see this route.
      */
     @NonNull
     @FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
-    public Set<String> getRequiredPermissions() {
+    public List<Set<String>> getRequiredPermissions() {
         return mRequiredPermissions;
     }
 
@@ -1119,7 +1126,8 @@
                 .append(", allowedPackages=")
                 .append(String.join(",", mAllowedPackages))
                 .append(", mRequiredPermissions=")
-                .append(String.join(",", mRequiredPermissions))
+                .append(mRequiredPermissions.stream().map(set -> String.join(",", set)).collect(
+                                Collectors.joining("),(", "(", ")")))
                 .append(", suitabilityStatus=")
                 .append(mSuitabilityStatus)
                 .append(" }")
@@ -1153,7 +1161,10 @@
         dest.writeString(mProviderId);
         dest.writeBoolean(mIsVisibilityRestricted);
         dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
-        dest.writeString8Array(mRequiredPermissions.toArray(new String[0]));
+        dest.writeInt(mRequiredPermissions.size());
+        for (Set<String> permissionSet : mRequiredPermissions) {
+            dest.writeString8Array(permissionSet.toArray(new String[0]));
+        }
         dest.writeInt(mSuitabilityStatus);
     }
 
@@ -1302,7 +1313,7 @@
         private String mProviderId;
         private boolean mIsVisibilityRestricted;
         private Set<String> mAllowedPackages;
-        private Set<String> mRequiredPermissions;
+        private List<Set<String>> mRequiredPermissions;
         @SuitabilityStatus private int mSuitabilityStatus;
 
         /**
@@ -1328,7 +1339,7 @@
             mDeduplicationIds = Set.of();
             mAllowedPackages = Set.of();
             mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
-            mRequiredPermissions = Set.of();
+            mRequiredPermissions = List.of();
         }
 
         /**
@@ -1610,7 +1621,7 @@
         public Builder setVisibilityPublic() {
             mIsVisibilityRestricted = false;
             mAllowedPackages = Set.of();
-            mRequiredPermissions = Set.of();
+            mRequiredPermissions = List.of();
             return this;
         }
 
@@ -1637,13 +1648,31 @@
         /**
          * Limits the visibility of this route to holders of a set of permissions.
          *
+         * <p>Calls to this method override any previous calls of
+         * {@link #setRequiredPermissions(Set)} or {@link #setRequiredPermissions(List)}.
+         *
          * @param requiredPermissions the list of all permissions which must be held in order to
          *                            see this route.
          */
         @NonNull
         @FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
         public Builder setRequiredPermissions(@NonNull Set<String> requiredPermissions) {
-            mRequiredPermissions = Set.copyOf(requiredPermissions);
+            return setRequiredPermissions(List.of(requiredPermissions));
+        }
+
+        /**
+         * Limits the visibility of this route to holders of one of a set of permissions.
+         *
+         * <p>Calls to this method override any previous calls of
+         * {@link #setRequiredPermissions(Set)} or {@link #setRequiredPermissions(List)}.
+         *
+         * @param requiresOneOf a list of Sets of permissions. Holding all permissions in at least
+         *                      one of the Sets is required for the route to be visible.
+         */
+        @NonNull
+        @FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
+        public Builder setRequiredPermissions(@NonNull List<Set<String>> requiresOneOf) {
+            mRequiredPermissions = List.copyOf(requiresOneOf);
             return this;
         }
 
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index 7b0bd04..6a52bcb 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -75,11 +75,9 @@
         public static final String PARAMETER_SATURATION = "saturation";
 
         /**
-         * @hide
-         */
-        public static final String PARAMETER_COLOR = "color";
-        /**
-         * @hide
+         * The hue.
+         *
+         * <p>Type: INTEGER
          */
         public static final String PARAMETER_HUE = "hue";
 
@@ -89,47 +87,77 @@
         public static final String PARAMETER_BACKLIGHT = "backlight";
 
         /**
-         * @hide
+         * Adjust brightness in advance color engine. Similar to a "brightness" control on a TV
+         * but acts at a lower level.
+         *
+         * <p>Type: INTEGER
          */
         public static final String PARAMETER_COLOR_TUNER_BRIGHTNESS = "color_tuner_brightness";
 
         /**
-         * @hide
+         * Adjust saturation in advance color engine. Similar to a "saturation" control on a TV
+         * but acts at a lower level.
+         *
+         * <p>Type: INTEGER
          */
         public static final String PARAMETER_COLOR_TUNER_SATURATION = "color_tuner_saturation";
 
         /**
-         * @hide
+         * Adjust hue in advance color engine. Similar to a "hue" control on a TV but acts at a
+         * lower level.
+         *
+         * <p>Type: INTEGER
          */
         public static final String PARAMETER_COLOR_TUNER_HUE = "color_tuner_hue";
 
         /**
-         * @hide
+         * Advance setting for red offset. Adjust the black level of red color channels, it
+         * controls the minimum intensity of each color, affecting the shadows and
+         * dark areas of the image.
+         *
+         * <p>Type: INTEGER
          */
-        public static final String PARAMETER_COLOR_TUNER_REDO_FFSET = "color_tuner_red_offset";
+        public static final String PARAMETER_COLOR_TUNER_RED_OFFSET = "color_tuner_red_offset";
 
         /**
-         * @hide
+         * Advance setting for green offset. Adjust the black level of green color channels, it
+         * controls the minimum intensity of each color, affecting the shadows and dark
+         * areas of the image.
+         *
+         * <p>Type: INTEGER
          */
         public static final String PARAMETER_COLOR_TUNER_GREEN_OFFSET = "color_tuner_green_offset";
 
         /**
-         * @hide
+         * Advance setting for blue offset. Adjust the black level of blue color channels, it
+         * controls the minimum intensity of each color, affecting the shadows and dark areas
+         * of the image.
+         *
+         * <p>Type: INTEGER
          */
         public static final String PARAMETER_COLOR_TUNER_BLUE_OFFSET = "color_tuner_blue_offset";
 
         /**
-         * @hide
+         * Advance setting for red gain. Adjust the gain or amplification of the red color channels.
+         * They control the overall intensity and white balance of red.
+         *
+         * <p>Type: INTEGER
          */
         public static final String PARAMETER_COLOR_TUNER_RED_GAIN = "color_tuner_red_gain";
 
         /**
-         * @hide
+         * Advance setting for green gain. Adjust the gain or amplification of the green color
+         * channels. They control the overall intensity and white balance of green.
+         *
+         * <p>Type: INTEGER
          */
         public static final String PARAMETER_COLOR_TUNER_GREEN_GAIN = "color_tuner_green_gain";
 
         /**
-         * @hide
+         * Advance setting for blue gain. Adjust the gain or amplification of the blue color
+         * channels.They control the overall intensity and white balance of blue.
+         *
+         * <p>Type: INTEGER
          */
         public static final String PARAMETER_COLOR_TUNER_BLUE_GAIN = "color_tuner_blue_gain";
 
@@ -143,33 +171,54 @@
          */
         public static final String PARAMETER_AI_SUPER_RESOLUTION = "ai_super_resolution";
 
-        /**
-         * @hide
+        /** Noise reduction.
+         * (Off, Low, Medium, High)
+         * @see android.hardware.tv.mediaquality.QualityLevel
+         *
+         * <p>Type: STRING
          */
         public static final String PARAMETER_NOISE_REDUCTION = "noise_reduction";
 
         /**
-         * @hide
-         */
+         *  MPEG (moving picture experts group) noise reduction
+         *  (Off, Low, Medium, High)
+         *  @see android.hardware.tv.mediaquality.QualityLevel
+         *
+         *  <p>Type: STRING
+         *  */
         public static final String PARAMETER_MPEG_NOISE_REDUCTION = "mpeg_noise_reduction";
 
         /**
-         * @hide
+         * Refine the flesh colors in the pictures without affecting the other colors on the screen.
+         * (Off, Low, Medium, High)
+         * @see android.hardware.tv.mediaquality.QualityLevel
+         *
+         * <p>Type: STRING
          */
         public static final String PARAMETER_FLESH_TONE = "flesh_tone";
 
         /**
-         * @hide
+         * Contour noise reduction.
+         * (Off, Low, Medium, High)
+         * @see android.hardware.tv.mediaquality.QualityLevel
+         *
+         * <p>Type: STRING
          */
         public static final String PARAMETER_DECONTOUR = "decontour";
 
         /**
-         * @hide
+         *  Dynamically change picture luma to enhance contrast.
+         *  (Off, Low, Medium, High)
+         *  @see android.hardware.tv.mediaquality.QualityLevel
+         *
+         *  <p>Type: STRING
          */
         public static final String PARAMETER_DYNAMIC_LUMA_CONTROL = "dynamic_luma_control";
 
         /**
-         * @hide
+         *  Enable/disable film mode
+         *
+         *  <p>Type: BOOLEAN
          */
         public static final String PARAMETER_FILM_MODE = "film_mode";
 
@@ -179,25 +228,50 @@
         public static final String PARAMETER_BLACK_STRETCH = "black_stretch";
 
         /**
-         * @hide
+         *  Enable/disable blue color auto stretch
+         *
+         *  <p>Type: BOOLEAN
          */
         public static final String PARAMETER_BLUE_STRETCH = "blue_stretch";
 
         /**
-         * @hide
+         *  Enable/disable the overall color tuning feature.
+         *
+         *  <p>Type: BOOLEAN
          */
         public static final String PARAMETER_COLOR_TUNE = "color_tune";
 
         /**
-         * @hide
+         *  Adjust color temperature type
+         *
+         *  <p>Type: INTEGER
          */
         public static final String PARAMETER_COLOR_TEMPERATURE = "color_temperature";
 
         /**
-         * @hide
+         *  Enable/disable globe dimming.
+         *
+         *  <p>Type: BOOLEAN
          */
         public static final String PARAMETER_GLOBAL_DIMMING = "global_dimming";
 
+        /**
+         *  Enable/disable auto adjust picture parameter based on the TV content.
+         *
+         *  <p>Type: BOOLEAN
+         */
+        public static final String PARAMETER_AUTO_PICTURE_QUALITY_ENABLED =
+                "auto_picture_quality_enabled";
+
+        /**
+         * Enable/disable auto upscaling the picture quality. It analyzes the lower-resolution
+         * image and uses its knowledge to invent the missing pixel, make the image look sharper.
+         *
+         * <p>Type: BOOLEAN
+         */
+        public static final String PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED =
+                "auto_super_resolution_enabled";
+
         private PictureQuality() {
         }
     }
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 8dd8830..1ccadf9 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -393,6 +393,7 @@
     APerformanceHint_createSessionUsingConfig; # introduced=36
     APerformanceHint_notifyWorkloadIncrease; # introduced=36
     APerformanceHint_notifyWorkloadReset; # introduced=36
+    APerformanceHint_notifyWorkloadSpike; # introduced=36
     APerformanceHint_borrowSessionFromJava; # introduced=36
     APerformanceHint_setNativeSurfaces; # introduced=36
     AWorkDuration_create; # introduced=VanillaIceCream
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 608c01c..1945d90 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -214,6 +214,7 @@
     int sendHints(std::vector<hal::SessionHint>& hints, int64_t now, const char* debugName);
     int notifyWorkloadIncrease(bool cpu, bool gpu, const char* debugName);
     int notifyWorkloadReset(bool cpu, bool gpu, const char* debugName);
+    int notifyWorkloadSpike(bool cpu, bool gpu, const char* debugName);
     int setThreads(const int32_t* threadIds, size_t size);
     int getThreadIds(int32_t* const threadIds, size_t* size);
     int setPreferPowerEfficiency(bool enabled);
@@ -328,7 +329,7 @@
 
 bool APerformanceHintManager::canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) {
     mHintBudget =
-            std::max(kMaxLoadHintsPerInterval,
+            std::min(kMaxLoadHintsPerInterval,
                      mHintBudget +
                              static_cast<double>(now - mLastBudgetReplenish) * kReplenishRate);
     mLastBudgetReplenish = now;
@@ -600,6 +601,19 @@
     return sendHints(hints, now, debugName);
 }
 
+int APerformanceHintSession::notifyWorkloadSpike(bool cpu, bool gpu, const char* debugName) {
+    std::vector<hal::SessionHint> hints(2);
+    hints.clear();
+    if (cpu) {
+        hints.push_back(hal::SessionHint::CPU_LOAD_SPIKE);
+    }
+    if (gpu) {
+        hints.push_back(hal::SessionHint::GPU_LOAD_SPIKE);
+    }
+    int64_t now = ::android::uptimeNanos();
+    return sendHints(hints, now, debugName);
+}
+
 int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) {
     if (size == 0) {
         ALOGE("%s: the list of thread ids must not be empty.", __FUNCTION__);
@@ -1149,6 +1163,16 @@
     return session->notifyWorkloadReset(cpu, gpu, debugName);
 }
 
+int APerformanceHint_notifyWorkloadSpike(APerformanceHintSession* session, bool cpu, bool gpu,
+                                         const char* debugName) {
+    VALIDATE_PTR(session)
+    VALIDATE_PTR(debugName)
+    if (!useNewLoadHintBehavior()) {
+        return ENOTSUP;
+    }
+    return session->notifyWorkloadReset(cpu, gpu, debugName);
+}
+
 int APerformanceHint_setNativeSurfaces(APerformanceHintSession* session,
                                        ANativeWindow** nativeWindows, int nativeWindowsSize,
                                        ASurfaceControl** surfaceControls, int surfaceControlsSize) {
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index b8f574f..c166e73 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -299,6 +299,10 @@
     EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::GPU_LOAD_RESET))).Times(Exactly(1));
     result = APerformanceHint_notifyWorkloadReset(session, true, true, "Test hint");
     EXPECT_EQ(0, result);
+    EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::CPU_LOAD_SPIKE))).Times(Exactly(1));
+    EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::GPU_LOAD_SPIKE))).Times(Exactly(1));
+    result = APerformanceHint_notifyWorkloadSpike(session, true, true, "Test hint");
+    EXPECT_EQ(0, result);
 
     result = APerformanceHint_sendHint(session, static_cast<SessionHint>(-1));
     EXPECT_EQ(EINVAL, result);
diff --git a/nfc/lint-baseline.xml b/nfc/lint-baseline.xml
index dd7b03d..67b496e 100644
--- a/nfc/lint-baseline.xml
+++ b/nfc/lint-baseline.xml
@@ -2,215 +2,6 @@
 <issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `new android.nfc.cardemulation.AidGroup`"
-        errorLine1="        AidGroup aidGroup = new AidGroup(aids, category);"
-        errorLine2="                            ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="377"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
-        errorLine1="            return (group != null ? group.getAids() : null);"
-        errorLine2="                                          ~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="537"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
-        errorLine1="                return (group != null ? group.getAids() : null);"
-        errorLine2="                                              ~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="547"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
-        errorLine1="            return (serviceInfo != null ? serviceInfo.getAids() : null);"
-        errorLine2="                                                      ~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="714"
-            column="55"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
-        errorLine1="                return (serviceInfo != null ? serviceInfo.getAids() : null);"
-        errorLine2="                                                          ~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="724"
-            column="59"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
-        errorLine1="                if (!serviceInfo.isOnHost()) {"
-        errorLine2="                                 ~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="755"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
-        errorLine1="                    return serviceInfo.getOffHostSecureElement() == null ?"
-        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="756"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
-        errorLine1='                            "OffHost" : serviceInfo.getOffHostSecureElement();'
-        errorLine2="                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="757"
-            column="53"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
-        errorLine1="                    if (!serviceInfo.isOnHost()) {"
-        errorLine2="                                     ~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="772"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
-        errorLine1="                        return serviceInfo.getOffHostSecureElement() == null ?"
-        errorLine2="                                           ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="773"
-            column="44"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
-        errorLine1='                                "Offhost" : serviceInfo.getOffHostSecureElement();'
-        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="774"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
-        errorLine1="            return (serviceInfo != null ? serviceInfo.getDescription() : null);"
-        errorLine2="                                                      ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="798"
-            column="55"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
-        errorLine1="                return (serviceInfo != null ? serviceInfo.getDescription() : null);"
-        errorLine2="                                                          ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="808"
-            column="59"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
-        errorLine1="        if (!activity.isResumed()) {"
-        errorLine2="                      ~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="1032"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
-        errorLine1="        if (!activity.isResumed()) {"
-        errorLine2="                      ~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
-            line="1066"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
-        errorLine1="            resumed = activity.isResumed();"
-        errorLine2="                               ~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/NfcActivityManager.java"
-            line="124"
-            column="32"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
-        errorLine1="        if (!activity.isResumed()) {"
-        errorLine2="                      ~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java"
-            line="2457"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
-        errorLine1="        if (!activity.isResumed()) {"
-        errorLine2="                      ~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
-            line="315"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
-        errorLine1="        if (!activity.isResumed()) {"
-        errorLine2="                      ~~~~~~~~~">
-        <location
-            file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
-            line="351"
-            column="23"/>
-    </issue>
-
-    <issue
         id="FlaggedApi"
         message="Method `NfcOemExtension()` is a flagged API and should be inside an `if (Flags.nfcOemExtension())` check (or annotate the surrounding method `NfcAdapter` with `@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) to transfer requirement to caller`)"
         errorLine1="        mNfcOemExtension = new NfcOemExtension(mContext, this);"
@@ -287,4 +78,4 @@
             column="44"/>
     </issue>
 
-</issues>
\ No newline at end of file
+</issues>
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
index 2ba93f1..560e751 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -25,6 +25,7 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -91,6 +92,7 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -336,20 +338,27 @@
     /**
      * Registers {@code observer} to listen for package failures. Add a new ObserverInternal for
      * this observer if it does not already exist.
+     * For executing mitigations observers will receive callback on the given executor.
      *
      * <p>Observers are expected to call this on boot. It does not specify any packages but
      * it will resume observing any packages requested from a previous boot.
-     * @hide
+     *
+     * @param observer instance of {@link PackageHealthObserver} for observing package failures
+     *                 and boot loops.
+     * @param executor Executor for the thread on which observers would receive callbacks
      */
-    public void registerHealthObserver(PackageHealthObserver observer) {
+    public void registerHealthObserver(@NonNull PackageHealthObserver observer,
+            @NonNull @CallbackExecutor Executor executor) {
         synchronized (sLock) {
             ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
             if (internalObserver != null) {
                 internalObserver.registeredObserver = observer;
+                internalObserver.observerExecutor = executor;
             } else {
                 internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
                         new ArrayList<>());
                 internalObserver.registeredObserver = observer;
+                internalObserver.observerExecutor = executor;
                 mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
                 syncState("added new observer");
             }
@@ -357,40 +366,53 @@
     }
 
     /**
-     * Starts observing the health of the {@code packages} for {@code observer} and notifies
-     * {@code observer} of any package failures within the monitoring duration.
+     * Starts observing the health of the {@code packages} for {@code observer}.
+     * Note: Observer needs to be registered with {@link #registerHealthObserver} before calling
+     * this API.
      *
      * <p>If monitoring a package supporting explicit health check, at the end of the monitoring
      * duration if {@link #onHealthCheckPassed} was never called,
-     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the package failed.
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the
+     * package failed.
      *
      * <p>If {@code observer} is already monitoring a package in {@code packageNames},
      * the monitoring window of that package will be reset to {@code durationMs} and the health
-     * check state will be reset to a default depending on if the package is contained in
-     * {@link mPackagesWithExplicitHealthCheckEnabled}.
+     * check state will be reset to a default.
      *
-     * <p>If {@code packageNames} is empty, this will be a no-op.
+     * <p>The {@code observer} must be registered with {@link #registerHealthObserver} before
+     * calling this method.
      *
-     * <p>If {@code durationMs} is less than 1, a default monitoring duration
-     * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
-     * @hide
+     * @param packageNames The list of packages to check. If this is empty, the call will be a
+     *                     no-op.
+     *
+     * @param timeoutMs The timeout after which Explicit Health Checks would not run. If this is
+     *                  less than 1, a default monitoring duration 2 days will be used.
+     *
+     * @throws IllegalStateException if the observer was not previously registered
      */
-    public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
-            long durationMs) {
+    public void startExplicitHealthCheck(@NonNull PackageHealthObserver observer,
+            @NonNull List<String> packageNames, long timeoutMs) {
+        synchronized (sLock) {
+            if (!mAllObservers.containsKey(observer.getUniqueIdentifier())) {
+                Slog.wtf(TAG, "No observer found, need to register the observer: "
+                        + observer.getUniqueIdentifier());
+                throw new IllegalStateException("Observer not registered");
+            }
+        }
         if (packageNames.isEmpty()) {
             Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
             return;
         }
-        if (durationMs < 1) {
-            Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
+        if (timeoutMs < 1) {
+            Slog.wtf(TAG, "Invalid duration " + timeoutMs + "ms for observer "
                     + observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
-            durationMs = DEFAULT_OBSERVING_DURATION_MS;
+            timeoutMs = DEFAULT_OBSERVING_DURATION_MS;
         }
 
         List<MonitoredPackage> packages = new ArrayList<>();
         for (int i = 0; i < packageNames.size(); i++) {
             // Health checks not available yet so health check state will start INACTIVE
-            MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), durationMs, false);
+            MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), timeoutMs, false);
             if (pkg != null) {
                 packages.add(pkg);
             } else {
@@ -423,9 +445,6 @@
                 }
             }
 
-            // Register observer in case not already registered
-            registerHealthObserver(observer);
-
             // Sync after we add the new packages to the observers. We may have received packges
             // requiring an earlier schedule than we are currently scheduled for.
             syncState("updated observers");
@@ -437,9 +456,8 @@
      * Unregisters {@code observer} from listening to package failure.
      * Additionally, this stops observing any packages that may have previously been observed
      * even from a previous boot.
-     * @hide
      */
-    public void unregisterHealthObserver(PackageHealthObserver observer) {
+    public void unregisterHealthObserver(@NonNull PackageHealthObserver observer) {
         mLongTaskHandler.post(() -> {
             synchronized (sLock) {
                 mAllObservers.remove(observer.getUniqueIdentifier());
@@ -485,7 +503,7 @@
                     for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
                         VersionedPackage versionedPackage = packages.get(pIndex);
                         // Observer that will receive failure for versionedPackage
-                        PackageHealthObserver currentObserverToNotify = null;
+                        ObserverInternal currentObserverToNotify = null;
                         int currentObserverImpact = Integer.MAX_VALUE;
                         MonitoredPackage currentMonitoredPackage = null;
 
@@ -506,7 +524,7 @@
                                         versionedPackage, failureReason, mitigationCount);
                                 if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                                         && impact < currentObserverImpact) {
-                                    currentObserverToNotify = registeredObserver;
+                                    currentObserverToNotify = observer;
                                     currentObserverImpact = impact;
                                     currentMonitoredPackage = p;
                                 }
@@ -515,18 +533,23 @@
 
                         // Execute action with least user impact
                         if (currentObserverToNotify != null) {
-                            int mitigationCount = 1;
+                            int mitigationCount;
                             if (currentMonitoredPackage != null) {
                                 currentMonitoredPackage.noteMitigationCallLocked();
                                 mitigationCount =
                                         currentMonitoredPackage.getMitigationCountLocked();
+                            } else {
+                                mitigationCount = 1;
                             }
                             if (Flags.recoverabilityDetection()) {
                                 maybeExecute(currentObserverToNotify, versionedPackage,
                                         failureReason, currentObserverImpact, mitigationCount);
                             } else {
-                                currentObserverToNotify.onExecuteHealthCheckMitigation(
-                                        versionedPackage, failureReason, mitigationCount);
+                                PackageHealthObserver registeredObserver =
+                                        currentObserverToNotify.registeredObserver;
+                                currentObserverToNotify.observerExecutor.execute(() ->
+                                        registeredObserver.onExecuteHealthCheckMitigation(
+                                                versionedPackage, failureReason, mitigationCount));
                             }
                         }
                     }
@@ -539,10 +562,11 @@
      * For native crashes or explicit health check failures, call directly into each observer to
      * mitigate the error without going through failure threshold logic.
      */
+    @GuardedBy("sLock")
     private void handleFailureImmediately(List<VersionedPackage> packages,
             @FailureReasons int failureReason) {
         VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
-        PackageHealthObserver currentObserverToNotify = null;
+        ObserverInternal currentObserverToNotify = null;
         int currentObserverImpact = Integer.MAX_VALUE;
         for (ObserverInternal observer: mAllObservers.values()) {
             PackageHealthObserver registeredObserver = observer.registeredObserver;
@@ -551,7 +575,7 @@
                         failingPackage, failureReason, 1);
                 if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                         && impact < currentObserverImpact) {
-                    currentObserverToNotify = registeredObserver;
+                    currentObserverToNotify = observer;
                     currentObserverImpact = impact;
                 }
             }
@@ -561,23 +585,30 @@
                 maybeExecute(currentObserverToNotify, failingPackage, failureReason,
                         currentObserverImpact, /*mitigationCount=*/ 1);
             } else {
-                currentObserverToNotify.onExecuteHealthCheckMitigation(failingPackage,
-                        failureReason, 1);
+                PackageHealthObserver registeredObserver =
+                        currentObserverToNotify.registeredObserver;
+                currentObserverToNotify.observerExecutor.execute(() ->
+                        registeredObserver.onExecuteHealthCheckMitigation(failingPackage,
+                                failureReason, 1));
+
             }
         }
     }
 
-    private void maybeExecute(PackageHealthObserver currentObserverToNotify,
+    private void maybeExecute(ObserverInternal currentObserverToNotify,
                               VersionedPackage versionedPackage,
                               @FailureReasons int failureReason,
                               int currentObserverImpact,
                               int mitigationCount) {
         if (allowMitigations(currentObserverImpact, versionedPackage)) {
+            PackageHealthObserver registeredObserver;
             synchronized (sLock) {
                 mLastMitigation = mSystemClock.uptimeMillis();
+                registeredObserver = currentObserverToNotify.registeredObserver;
             }
-            currentObserverToNotify.onExecuteHealthCheckMitigation(versionedPackage, failureReason,
-                    mitigationCount);
+            currentObserverToNotify.observerExecutor.execute(() ->
+                    registeredObserver.onExecuteHealthCheckMitigation(versionedPackage,
+                            failureReason, mitigationCount));
         }
     }
 
@@ -613,8 +644,7 @@
                     mBootThreshold.reset();
                 }
                 int mitigationCount = mBootThreshold.getMitigationCount() + 1;
-                PackageHealthObserver currentObserverToNotify = null;
-                ObserverInternal currentObserverInternal = null;
+                ObserverInternal currentObserverToNotify = null;
                 int currentObserverImpact = Integer.MAX_VALUE;
                 for (int i = 0; i < mAllObservers.size(); i++) {
                     final ObserverInternal observer = mAllObservers.valueAt(i);
@@ -626,25 +656,31 @@
                                 : registeredObserver.onBootLoop(mitigationCount);
                         if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
                                 && impact < currentObserverImpact) {
-                            currentObserverToNotify = registeredObserver;
-                            currentObserverInternal = observer;
+                            currentObserverToNotify = observer;
                             currentObserverImpact = impact;
                         }
                     }
                 }
+
                 if (currentObserverToNotify != null) {
+                    PackageHealthObserver registeredObserver =
+                            currentObserverToNotify.registeredObserver;
                     if (Flags.recoverabilityDetection()) {
                         int currentObserverMitigationCount =
-                                currentObserverInternal.getBootMitigationCount() + 1;
-                        currentObserverInternal.setBootMitigationCount(
+                                currentObserverToNotify.getBootMitigationCount() + 1;
+                        currentObserverToNotify.setBootMitigationCount(
                                 currentObserverMitigationCount);
                         saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
-                        currentObserverToNotify.onExecuteBootLoopMitigation(
-                                currentObserverMitigationCount);
+                        currentObserverToNotify.observerExecutor
+                                .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
+                                        currentObserverMitigationCount));
                     } else {
                         mBootThreshold.setMitigationCount(mitigationCount);
                         mBootThreshold.saveMitigationCountToMetadata();
-                        currentObserverToNotify.onExecuteBootLoopMitigation(mitigationCount);
+                        currentObserverToNotify.observerExecutor
+                                .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
+                                        mitigationCount));
+
                     }
                 }
             }
@@ -879,7 +915,7 @@
          * passed to observers in these API.
          *
          * <p> A persistent observer may choose to start observing certain failing packages, even if
-         * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
+         * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
          */
         default boolean mayObservePackage(@NonNull String packageName) {
             return false;
@@ -1136,8 +1172,10 @@
                         if (versionedPkg != null) {
                             Slog.i(TAG,
                                     "Explicit health check failed for package " + versionedPkg);
-                            registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
-                                    PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+                            observer.observerExecutor.execute(() ->
+                                    registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
+                                            PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
+                                            1));
                         }
                     }
                 }
@@ -1395,6 +1433,7 @@
         @Nullable
         @GuardedBy("sLock")
         public PackageHealthObserver registeredObserver;
+        public Executor observerExecutor;
         private int mMitigationCount;
 
         ObserverInternal(String name, List<MonitoredPackage> packages) {
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
index 992f581..bad6ab7 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
@@ -160,7 +160,7 @@
     /** Register the Rescue Party observer as a Package Watchdog health observer */
     public static void registerHealthObserver(Context context) {
         PackageWatchdog.getInstance(context).registerHealthObserver(
-                RescuePartyObserver.getInstance(context));
+                RescuePartyObserver.getInstance(context), context.getMainExecutor());
     }
 
     private static boolean isDisabled() {
@@ -313,7 +313,7 @@
             callingPackageList.addAll(callingPackages);
             Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
                     + updatedNamespace);
-            PackageWatchdog.getInstance(context).startObservingHealth(
+            PackageWatchdog.getInstance(context).startExplicitHealthCheck(
                     rescuePartyObserver,
                     callingPackageList,
                     DEFAULT_OBSERVING_DURATION_MS);
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 311def8..c80a1a4 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -111,7 +111,8 @@
         dataDir.mkdirs();
         mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
         mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
-        PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+        PackageWatchdog.getInstance(mContext).registerHealthObserver(this,
+                context.getMainExecutor());
 
         if (SystemProperties.getBoolean("sys.boot_completed", false)) {
             // Load the value from the file if system server has crashed and restarted
@@ -273,16 +274,6 @@
         Preconditions.checkState(mHandler.getLooper().isCurrentThread());
     }
 
-    /**
-     * Start observing health of {@code packages} for {@code durationMs}.
-     * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
-     */
-    @AnyThread
-    @NonNull
-    public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
-        PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
-    }
-
     @AnyThread
     @NonNull
     public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) {
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
index 88fe36c..4fea937 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
@@ -87,6 +87,7 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -362,7 +363,7 @@
      * it will resume observing any packages requested from a previous boot.
      * @hide
      */
-    public void registerHealthObserver(PackageHealthObserver observer) {
+    public void registerHealthObserver(PackageHealthObserver observer, Executor ignoredExecutor) {
         synchronized (mLock) {
             ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
             if (internalObserver != null) {
@@ -396,7 +397,7 @@
      * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
      * @hide
      */
-    public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
+    public void startExplicitHealthCheck(PackageHealthObserver observer, List<String> packageNames,
             long durationMs) {
         if (packageNames.isEmpty()) {
             Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
@@ -445,7 +446,7 @@
             }
 
             // Register observer in case not already registered
-            registerHealthObserver(observer);
+            registerHealthObserver(observer, null);
 
             // Sync after we add the new packages to the observers. We may have received packges
             // requiring an earlier schedule than we are currently scheduled for.
@@ -861,7 +862,7 @@
          * otherwise
          *
          * <p> A persistent observer may choose to start observing certain failing packages, even if
-         * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
+         * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
          */
         default boolean mayObservePackage(@NonNull String packageName) {
             return false;
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
index f757236..2bb72fb 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
@@ -166,7 +166,7 @@
     /** Register the Rescue Party observer as a Package Watchdog health observer */
     public static void registerHealthObserver(Context context) {
         PackageWatchdog.getInstance(context).registerHealthObserver(
-                RescuePartyObserver.getInstance(context));
+                RescuePartyObserver.getInstance(context), null);
     }
 
     private static boolean isDisabled() {
@@ -387,7 +387,7 @@
             callingPackageList.addAll(callingPackages);
             Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
                     + updatedNamespace);
-            PackageWatchdog.getInstance(context).startObservingHealth(
+            PackageWatchdog.getInstance(context).startExplicitHealthCheck(
                     rescuePartyObserver,
                     callingPackageList,
                     DEFAULT_OBSERVING_DURATION_MS);
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 7445534..0692cdb 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -110,7 +110,7 @@
         dataDir.mkdirs();
         mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
         mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
-        PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+        PackageWatchdog.getInstance(mContext).registerHealthObserver(this, null);
         mApexManager = apexManager;
 
         if (SystemProperties.getBoolean("sys.boot_completed", false)) {
@@ -284,7 +284,7 @@
     @AnyThread
     @NonNull
     public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
-        PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
+        PackageWatchdog.getInstance(mContext).startExplicitHealthCheck(this, packages, durationMs);
     }
 
     @AnyThread
diff --git a/packages/NeuralNetworks/framework/Android.bp b/packages/NeuralNetworks/framework/Android.bp
new file mode 100644
index 0000000..6f45daa
--- /dev/null
+++ b/packages/NeuralNetworks/framework/Android.bp
@@ -0,0 +1,30 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "framework-ondeviceintelligence-sources",
+    srcs: [
+        "java/**/*.aidl",
+        "java/**/*.java",
+    ],
+    path: "java",
+    visibility: [
+        "//frameworks/base:__subpackages__",
+        "//packages/modules/NeuralNetworks:__subpackages__",
+    ],
+}
diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java
similarity index 96%
rename from core/java/android/app/ondeviceintelligence/DownloadCallback.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java
index 30c6e19..95fb288 100644
--- a/core/java/android/app/ondeviceintelligence/DownloadCallback.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -23,8 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.PersistableBundle;
-
-import androidx.annotation.IntDef;
+import android.annotation.IntDef;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -53,14 +52,14 @@
 
     /**
      * Sent when feature download has been initiated already, hence no need to request download
-     * again. Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check if
+     * again. Caller can query {@link OnDeviceIntelligenceManager#getFeatureDetails} to check if
      * download has been completed.
      */
     int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3;
 
     /**
      * Sent when feature download did not start due to errors (e.g. remote exception of features not
-     * available). Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check
+     * available). Caller can query {@link OnDeviceIntelligenceManager#getFeatureDetails} to check
      * if feature-status is {@link FeatureDetails#FEATURE_STATUS_DOWNLOADABLE}.
      */
     int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4;
@@ -72,7 +71,7 @@
             DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE,
             DOWNLOAD_FAILURE_STATUS_DOWNLOADING,
             DOWNLOAD_FAILURE_STATUS_UNAVAILABLE
-    }, open = true)
+    })
     @Retention(RetentionPolicy.SOURCE)
     @interface DownloadFailureStatus {
     }
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl
similarity index 93%
rename from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl
index 0589bf8..47cfb4a 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl
@@ -19,4 +19,4 @@
 /**
   * @hide
   */
-parcelable FeatureDetails;
+@JavaOnlyStableParcelable parcelable Feature;
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java
similarity index 96%
rename from core/java/android/app/ondeviceintelligence/Feature.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java
index bcc56073..88f4de29 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java
@@ -26,6 +26,8 @@
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 
+import java.util.Objects;
+
 /**
  * Represents a typical feature associated with on-device intelligence.
  *
@@ -56,9 +58,8 @@
         this.mModelName = modelName;
         this.mType = type;
         this.mVariant = variant;
-        this.mFeatureParams = featureParams;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mFeatureParams);
+        this.mFeatureParams = Objects.requireNonNull(featureParams,
+                "featureParams should be non-null.");
     }
 
     /** Returns the unique and immutable identifier of this feature. */
@@ -167,8 +168,6 @@
         this.mType = type;
         this.mVariant = variant;
         this.mFeatureParams = featureParams;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mFeatureParams);
     }
 
     public static final @NonNull Parcelable.Creator<Feature> CREATOR
@@ -200,6 +199,7 @@
 
         /**
          * Provides a builder instance to create a feature for given id.
+         *
          * @param id the unique identifier for the feature.
          */
         public Builder(int id) {
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl
similarity index 92%
copy from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
copy to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl
index 0589bf8..c5b3532 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl
@@ -19,4 +19,4 @@
 /**
   * @hide
   */
-parcelable FeatureDetails;
+@JavaOnlyStableParcelable parcelable FeatureDetails;
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java
similarity index 87%
rename from core/java/android/app/ondeviceintelligence/FeatureDetails.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java
index 44930f2..063cfb8 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -19,18 +19,18 @@
 import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 
-import androidx.annotation.IntDef;
-
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.text.MessageFormat;
+import java.util.Objects;
 
 /**
  * Represents a status of a requested {@link Feature}.
@@ -69,7 +69,7 @@
             FEATURE_STATUS_DOWNLOADING,
             FEATURE_STATUS_AVAILABLE,
             FEATURE_STATUS_SERVICE_UNAVAILABLE
-    }, open = true)
+    })
     @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Status {
@@ -79,18 +79,12 @@
             @Status int featureStatus,
             @NonNull PersistableBundle featureDetailParams) {
         this.mFeatureStatus = featureStatus;
-        com.android.internal.util.AnnotationValidations.validate(
-                Status.class, null, mFeatureStatus);
-        this.mFeatureDetailParams = featureDetailParams;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mFeatureDetailParams);
+        this.mFeatureDetailParams = Objects.requireNonNull(featureDetailParams);
     }
 
     public FeatureDetails(
             @Status int featureStatus) {
         this.mFeatureStatus = featureStatus;
-        com.android.internal.util.AnnotationValidations.validate(
-                Status.class, null, mFeatureStatus);
         this.mFeatureDetailParams = new PersistableBundle();
     }
 
@@ -155,11 +149,7 @@
                 PersistableBundle.CREATOR);
 
         this.mFeatureStatus = status;
-        com.android.internal.util.AnnotationValidations.validate(
-                Status.class, null, mFeatureStatus);
         this.mFeatureDetailParams = persistableBundle;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mFeatureDetailParams);
     }
 
 
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
similarity index 82%
copy from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
copy to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
index 0589bf8..1fe201f 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
@@ -5,7 +5,7 @@
  * 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
+ *      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,
@@ -17,6 +17,8 @@
 package android.app.ondeviceintelligence;
 
 /**
-  * @hide
-  */
-parcelable FeatureDetails;
+ * @hide
+ */
+oneway interface ICancellationSignal {
+    void cancel();
+}
diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
similarity index 97%
rename from core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index 1977a39..fac5ec6 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -16,8 +16,8 @@
 
  package android.app.ondeviceintelligence;
 
- import com.android.internal.infra.AndroidFuture;
- import android.os.ICancellationSignal;
+ import com.android.modules.utils.AndroidFuture;
+ import android.app.ondeviceintelligence.ICancellationSignal;
  import android.os.ParcelFileDescriptor;
  import android.os.PersistableBundle;
  import android.os.RemoteCallback;
diff --git a/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl
new file mode 100644
index 0000000..6f07693
--- /dev/null
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl
@@ -0,0 +1,24 @@
+/*
+* Copyright 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.ondeviceintelligence;
+
+import android.os.Bundle;
+
+/* @hide */
+oneway interface IRemoteCallback {
+    void sendResult(in Bundle data);
+}
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IResponseCallback.aidl
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IResponseCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl
similarity index 92%
copy from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
copy to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl
index 0589bf8..6f63254 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl
@@ -19,4 +19,4 @@
 /**
   * @hide
   */
-parcelable FeatureDetails;
+@JavaOnlyStableParcelable parcelable InferenceInfo;
diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/InferenceInfo.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
similarity index 97%
rename from core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
index 03ff563a..2881c9d 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
@@ -20,13 +20,14 @@
 import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.PersistableBundle;
 
-import androidx.annotation.IntDef;
-
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
@@ -124,8 +125,9 @@
                     PROCESSING_ERROR_SERVICE_UNAVAILABLE,
                     ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
                     PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
-            }, open = true)
+            })
     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    @Retention(RetentionPolicy.SOURCE)
     @interface OnDeviceIntelligenceError {
     }
 
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java
new file mode 100644
index 0000000..7d35dd7
--- /dev/null
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java
@@ -0,0 +1,54 @@
+/*
+ * 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.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for performing registration for OnDeviceIntelligence service.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public class OnDeviceIntelligenceFrameworkInitializer {
+    private OnDeviceIntelligenceFrameworkInitializer() {
+    }
+
+    /**
+     * Called by {@link SystemServiceRegistry}'s static initializer and registers
+     * OnDeviceIntelligence service to {@link Context}, so that {@link Context#getSystemService} can
+     * return them.
+     *
+     * @throws IllegalStateException if this is called from anywhere besides {@link
+     *                               SystemServiceRegistry}
+     */
+    public static void registerServiceWrappers() {
+        SystemServiceRegistry.registerContextAwareService(Context.ON_DEVICE_INTELLIGENCE_SERVICE,
+                OnDeviceIntelligenceManager.class,
+                (context, serviceBinder) -> {
+                    IOnDeviceIntelligenceManager manager =
+                            IOnDeviceIntelligenceManager.Stub.asInterface(serviceBinder);
+                    return new OnDeviceIntelligenceManager(context, manager);
+                });
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
similarity index 88%
rename from core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index 91651e3..dc0665a 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -23,18 +23,18 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.ondeviceintelligence.utils.BinderUtils;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.IBinder;
-import android.os.ICancellationSignal;
 import android.os.OutcomeReceiver;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
@@ -42,9 +42,7 @@
 import android.system.OsConstants;
 import android.util.Log;
 
-import androidx.annotation.IntDef;
-
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -80,10 +78,39 @@
     public static final String AUGMENT_REQUEST_CONTENT_BUNDLE_KEY =
             "AugmentRequestContentBundleKey";
 
+    /**
+     * Timeout to be used for unbinding to the configured remote {@link
+     * android.service.ondeviceintelligence.OnDeviceIntelligenceService} if there are no requests in
+     * the queue. A value of -1 represents to never unbind.
+     *
+     * @hide
+     */
+    public static final String ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS =
+        "on_device_intelligence_unbind_timeout_ms";
+
+    /**
+     * Timeout that represents maximum idle time before which a callback should be populated.
+     *
+     * @hide
+     */
+    public static final String ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS =
+        "on_device_intelligence_idle_timeout_ms";
+
+    /**
+     * Timeout to be used for unbinding to the configured remote {@link
+     * android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} if there are no
+     * requests in the queue. A value of -1 represents to never unbind.
+     *
+     * @hide
+     */
+    public static final String ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS =
+        "on_device_inference_unbind_timeout_ms";
+
     private static final String TAG = "OnDeviceIntelligence";
     private final Context mContext;
     private final IOnDeviceIntelligenceManager mService;
 
+
     /**
      * @hide
      */
@@ -105,11 +132,11 @@
         try {
             RemoteCallback callback = new RemoteCallback(result -> {
                 if (result == null) {
-                    Binder.withCleanCallingIdentity(
+                    BinderUtils.withCleanCallingIdentity(
                             () -> callbackExecutor.execute(() -> versionConsumer.accept(0)));
                 }
                 long version = result.getLong(API_VERSION_BUNDLE_KEY);
-                Binder.withCleanCallingIdentity(
+                BinderUtils.withCleanCallingIdentity(
                         () -> callbackExecutor.execute(() -> versionConsumer.accept(version)));
             });
             mService.getVersion(callback);
@@ -151,14 +178,14 @@
                     new IFeatureCallback.Stub() {
                         @Override
                         public void onSuccess(Feature result) {
-                            Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                            BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                                     () -> featureReceiver.onResult(result)));
                         }
 
                         @Override
                         public void onFailure(int errorCode, String errorMessage,
                                 PersistableBundle errorParams) {
-                            Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                            BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                                     () -> featureReceiver.onError(
                                             new OnDeviceIntelligenceException(
                                                     errorCode, errorMessage, errorParams))));
@@ -185,14 +212,14 @@
                     new IListFeaturesCallback.Stub() {
                         @Override
                         public void onSuccess(List<Feature> result) {
-                            Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                            BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                                     () -> featureListReceiver.onResult(result)));
                         }
 
                         @Override
                         public void onFailure(int errorCode, String errorMessage,
                                 PersistableBundle errorParams) {
-                            Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                            BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                                     () -> featureListReceiver.onError(
                                             new OnDeviceIntelligenceException(
                                                     errorCode, errorMessage, errorParams))));
@@ -223,14 +250,14 @@
 
                 @Override
                 public void onSuccess(FeatureDetails result) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> featureDetailsReceiver.onResult(result)));
                 }
 
                 @Override
                 public void onFailure(int errorCode, String errorMessage,
                         PersistableBundle errorParams) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> featureDetailsReceiver.onError(
                                     new OnDeviceIntelligenceException(errorCode,
                                             errorMessage, errorParams))));
@@ -268,27 +295,27 @@
 
                 @Override
                 public void onDownloadStarted(long bytesToDownload) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> callback.onDownloadStarted(bytesToDownload)));
                 }
 
                 @Override
                 public void onDownloadProgress(long bytesDownloaded) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> callback.onDownloadProgress(bytesDownloaded)));
                 }
 
                 @Override
                 public void onDownloadFailed(int failureStatus, String errorMessage,
                         PersistableBundle errorParams) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> callback.onDownloadFailed(failureStatus, errorMessage,
                                     errorParams)));
                 }
 
                 @Override
                 public void onDownloadCompleted(PersistableBundle downloadParams) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> callback.onDownloadCompleted(downloadParams)));
                 }
             };
@@ -325,14 +352,14 @@
             ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
                 @Override
                 public void onSuccess(TokenInfo tokenInfo) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> outcomeReceiver.onResult(tokenInfo)));
                 }
 
                 @Override
                 public void onFailure(int errorCode, String errorMessage,
                         PersistableBundle errorParams) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> outcomeReceiver.onError(
                                     new OnDeviceIntelligenceException(
                                             errorCode, errorMessage, errorParams))));
@@ -377,7 +404,7 @@
             IResponseCallback callback = new IResponseCallback.Stub() {
                 @Override
                 public void onSuccess(@InferenceParams Bundle result) {
-                    Binder.withCleanCallingIdentity(() -> {
+                    BinderUtils.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(() -> processingCallback.onResult(result));
                     });
                 }
@@ -385,7 +412,7 @@
                 @Override
                 public void onFailure(int errorCode, String errorMessage,
                         PersistableBundle errorParams) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> processingCallback.onError(
                                     new OnDeviceIntelligenceException(
                                             errorCode, errorMessage, errorParams))));
@@ -394,7 +421,7 @@
                 @Override
                 public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request,
                         @NonNull RemoteCallback contentCallback) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> processingCallback.onDataAugmentRequest(request, result -> {
                                 Bundle bundle = new Bundle();
                                 bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
@@ -447,7 +474,7 @@
             IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
                 @Override
                 public void onNewContent(@InferenceParams Bundle result) {
-                    Binder.withCleanCallingIdentity(() -> {
+                    BinderUtils.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(
                                 () -> streamingProcessingCallback.onPartialResult(result));
                     });
@@ -455,7 +482,7 @@
 
                 @Override
                 public void onSuccess(@InferenceParams Bundle result) {
-                    Binder.withCleanCallingIdentity(() -> {
+                    BinderUtils.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(
                                 () -> streamingProcessingCallback.onResult(result));
                     });
@@ -464,7 +491,7 @@
                 @Override
                 public void onFailure(int errorCode, String errorMessage,
                         PersistableBundle errorParams) {
-                    Binder.withCleanCallingIdentity(() -> {
+                    BinderUtils.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(
                                 () -> streamingProcessingCallback.onError(
                                         new OnDeviceIntelligenceException(
@@ -476,7 +503,7 @@
                 @Override
                 public void onDataAugmentRequest(@NonNull @InferenceParams Bundle content,
                         @NonNull RemoteCallback contentCallback) {
-                    Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                    BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> streamingProcessingCallback.onDataAugmentRequest(content,
                                     contentResponse -> {
                                         Bundle bundle = new Bundle();
@@ -537,7 +564,7 @@
             REQUEST_TYPE_INFERENCE,
             REQUEST_TYPE_PREPARE,
             REQUEST_TYPE_EMBEDDINGS
-    }, open = true)
+    })
     @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
             ElementType.FIELD})
     @Retention(RetentionPolicy.SOURCE)
@@ -614,8 +641,17 @@
                     if (error != null || cancellationTransport == null) {
                         Log.e(TAG, "Unable to receive the remote cancellation signal.", error);
                     } else {
-                        cancellationSignal.setRemote(
-                                ICancellationSignal.Stub.asInterface(cancellationTransport));
+                        ICancellationSignal remoteCancellationSignal =
+                                ICancellationSignal.Stub.asInterface(cancellationTransport);
+                        cancellationSignal.setOnCancelListener(
+                                () -> {
+                                    try {
+                                        remoteCancellationSignal.cancel();
+                                    } catch (RemoteException e) {
+                                        Log.w(TAG, "Unable to propagate cancellation signal.",
+                                                e);
+                                    }
+                                });
                     }
                 }, callbackExecutor);
         return cancellationFuture;
@@ -638,6 +674,4 @@
                 }, executor);
         return processingSignalFuture;
     }
-
-
-}
+}
\ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingCallback.java
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/ProcessingCallback.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingCallback.java
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingSignal.java
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/ProcessingSignal.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingSignal.java
diff --git a/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl
similarity index 93%
rename from core/java/android/app/ondeviceintelligence/TokenInfo.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl
index 2c19c1e..599b337 100644
--- a/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl
@@ -19,4 +19,4 @@
 /**
   * @hide
   */
-parcelable TokenInfo;
+@JavaOnlyStableParcelable parcelable TokenInfo;
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.java
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/TokenInfo.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java
new file mode 100644
index 0000000..2916f03
--- /dev/null
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java
@@ -0,0 +1,96 @@
+/*
+ * 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.ondeviceintelligence.utils;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+
+import java.util.function.Supplier;
+
+/**
+ * Collection of utilities for {@link Binder} and related classes.
+ * @hide
+ */
+public class BinderUtils {
+    /**
+     * Convenience method for running the provided action enclosed in
+     * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
+     *
+     * Any exception thrown by the given action will be caught and rethrown after the call to
+     * {@link Binder#restoreCallingIdentity}
+     *
+     * Note that this is copied from Binder#withCleanCallingIdentity with minor changes
+     * since it is not public.
+     *
+     * @hide
+     */
+    public static final <T extends Exception> void withCleanCallingIdentity(
+            @NonNull ThrowingRunnable<T> action) throws T {
+        final long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            action.run();
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    /**
+     * Like a Runnable, but declared to throw an exception.
+     *
+     * @param <T> The exception class which is declared to be thrown.
+     */
+    @FunctionalInterface
+    public interface ThrowingRunnable<T extends Exception> {
+        /** @see java.lang.Runnable */
+        void run() throws T;
+    }
+
+    /**
+     * Convenience method for running the provided action enclosed in
+     * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} returning the
+     * result.
+     *
+     * <p>Any exception thrown by the given action will be caught and rethrown after
+     * the call to {@link Binder#restoreCallingIdentity}.
+     *
+     * Note that this is copied from Binder#withCleanCallingIdentity with minor changes
+     * since it is not public.
+     *
+     * @hide
+     */
+    public static final <T, E extends Exception> T withCleanCallingIdentity(
+            @NonNull ThrowingSupplier<T, E> action) throws E {
+        final long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            return action.get();
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    /**
+     * An equivalent of {@link Supplier}
+     *
+     * @param <T> The class which is declared to be returned.
+     * @param <E> The exception class which is declared to be thrown.
+     */
+    @FunctionalInterface
+    public interface ThrowingSupplier<T, E extends Exception> {
+        /** @see java.util.function.Supplier */
+        T get() throws E;
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
similarity index 95%
rename from core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
index 45c4350..cba18c1 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -18,14 +18,14 @@
 
 import android.os.PersistableBundle;
 import android.os.ParcelFileDescriptor;
-import android.os.ICancellationSignal;
+import android.app.ondeviceintelligence.ICancellationSignal;
 import android.os.RemoteCallback;
 import android.app.ondeviceintelligence.IDownloadCallback;
 import android.app.ondeviceintelligence.Feature;
 import android.app.ondeviceintelligence.IFeatureCallback;
 import android.app.ondeviceintelligence.IListFeaturesCallback;
 import android.app.ondeviceintelligence.IFeatureDetailsCallback;
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
 import android.service.ondeviceintelligence.IRemoteProcessingService;
 
 
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
similarity index 93%
rename from core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 1af3b0f..504fdd9 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -21,11 +21,11 @@
 import android.app.ondeviceintelligence.ITokenInfoCallback;
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.app.ondeviceintelligence.Feature;
-import android.os.IRemoteCallback;
-import android.os.ICancellationSignal;
+import android.app.ondeviceintelligence.IRemoteCallback;
+import android.app.ondeviceintelligence.ICancellationSignal;
 import android.os.PersistableBundle;
 import android.os.Bundle;
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
 import android.service.ondeviceintelligence.IRemoteStorageService;
 import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
 
diff --git a/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
similarity index 100%
rename from core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
diff --git a/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
similarity index 100%
rename from core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
diff --git a/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
similarity index 95%
rename from core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
index a6f49e1..253df89 100644
--- a/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
@@ -20,7 +20,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteCallback;
 
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
 
 /**
  * Interface for a concrete implementation to provide access to storage read access
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
similarity index 67%
rename from core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index d82fe1c..6907e2b 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -18,8 +18,6 @@
 
 import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
 
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
 import android.annotation.CallSuper;
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
@@ -27,11 +25,11 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.app.Service;
 import android.app.ondeviceintelligence.DownloadCallback;
 import android.app.ondeviceintelligence.Feature;
 import android.app.ondeviceintelligence.FeatureDetails;
+import android.app.ondeviceintelligence.ICancellationSignal;
 import android.app.ondeviceintelligence.IDownloadCallback;
 import android.app.ondeviceintelligence.IFeatureCallback;
 import android.app.ondeviceintelligence.IFeatureDetailsCallback;
@@ -39,14 +37,14 @@
 import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
 import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
 import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.StateParams;
+import android.app.ondeviceintelligence.utils.BinderUtils;
 import android.content.Intent;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.ICancellationSignal;
 import android.os.Looper;
+import android.os.Message;
 import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
@@ -55,10 +53,11 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -92,6 +91,18 @@
 @SystemApi
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
 public abstract class OnDeviceIntelligenceService extends Service {
+    private static final int MSG_ON_READY = 1;
+    private static final int MSG_GET_VERSION = 2;
+    private static final int MSG_LIST_FEATURES = 3;
+    private static final int MSG_GET_FEATURE = 4;
+    private static final int MSG_GET_FEATURE_DETAILS = 5;
+    private static final int MSG_DOWNLOAD_FEATURE = 6;
+    private static final int MSG_GET_READ_ONLY_FILE_DESCRIPTOR = 7;
+    private static final int MSG_GET_READ_ONLY_FEATURE_FILE_DESCRIPTOR_MAP = 8;
+    private static final int MSG_REGISTER_REMOTE_SERVICES = 9;
+    private static final int MSG_INFERENCE_SERVICE_CONNECTED = 10;
+    private static final int MSG_INFERENCE_SERVICE_DISCONNECTED = 11;
+
     private static final String TAG = OnDeviceIntelligenceService.class.getSimpleName();
 
     private volatile IRemoteProcessingService mRemoteProcessingService;
@@ -101,19 +112,71 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */);
+        mHandler = new Handler(Looper.getMainLooper()) {
+            @Override
+            public void handleMessage(@NonNull Message msg) {
+                switch (msg.what) {
+                    case MSG_ON_READY:
+                        OnDeviceIntelligenceService.this.onReady();
+                        break;
+                    case MSG_GET_VERSION:
+                        OnDeviceIntelligenceService.this.onGetVersion(
+                                (LongConsumer) msg.obj);
+                        break;
+                    case MSG_LIST_FEATURES:
+                        OnDeviceIntelligenceService.this.onListFeatures(
+                                msg.arg1,
+                                (OutcomeReceiver<List<Feature>, OnDeviceIntelligenceException>) msg.obj);
+                        break;
+                    case MSG_GET_FEATURE:
+                        GetFeatureParams params = (GetFeatureParams) msg.obj;
+                        OnDeviceIntelligenceService.this.onGetFeature(
+                                msg.arg1,
+                                msg.arg2,
+                                params.callback);
+                        break;
+                    case MSG_GET_FEATURE_DETAILS:
+                        FeatureDetailsParams detailsParams = (FeatureDetailsParams) msg.obj;
+                        OnDeviceIntelligenceService.this.onGetFeatureDetails(
+                                msg.arg1,
+                                detailsParams.feature,
+                                detailsParams.callback);
+                        break;
+                    case MSG_DOWNLOAD_FEATURE:
+                        DownloadParams downloadParams = (DownloadParams) msg.obj;
+                        OnDeviceIntelligenceService.this.onDownloadFeature(
+                                msg.arg1,
+                                downloadParams.feature,
+                                downloadParams.cancellationSignal,
+                                downloadParams.callback);
+                        break;
+                    case MSG_GET_READ_ONLY_FILE_DESCRIPTOR:
+                        FileDescriptorParams fdParams = (FileDescriptorParams) msg.obj;
+                        OnDeviceIntelligenceService.this.onGetReadOnlyFileDescriptor(
+                                fdParams.fileName,
+                                fdParams.future);
+                        break;
+                    case MSG_GET_READ_ONLY_FEATURE_FILE_DESCRIPTOR_MAP:
+                        FeatureFileDescriptorParams ffdParams =
+                                (FeatureFileDescriptorParams) msg.obj;
+                        OnDeviceIntelligenceService.this.onGetReadOnlyFeatureFileDescriptorMap(
+                                ffdParams.feature,
+                                ffdParams.consumer);
+                        break;
+                    case MSG_REGISTER_REMOTE_SERVICES:
+                        mRemoteProcessingService = (IRemoteProcessingService) msg.obj;
+                        break;
+                    case MSG_INFERENCE_SERVICE_CONNECTED:
+                        OnDeviceIntelligenceService.this.onInferenceServiceConnected();
+                        break;
+                    case MSG_INFERENCE_SERVICE_DISCONNECTED:
+                        OnDeviceIntelligenceService.this.onInferenceServiceDisconnected();
+                        break;
+                }
+            }
+        };
     }
 
-    /**
-     * The {@link Intent} that must be declared as handled by the service. To be supported, the
-     * service must also require the
-     * {@link android.Manifest.permission#BIND_ON_DEVICE_INTELLIGENCE_SERVICE}
-     * permission so that other applications can not abuse it.
-     */
-    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
-    public static final String SERVICE_INTERFACE =
-            "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
-
 
     /**
      * @hide
@@ -126,45 +189,37 @@
                 /** {@inheritDoc} */
                 @Override
                 public void ready() {
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(OnDeviceIntelligenceService::onReady,
-                                    OnDeviceIntelligenceService.this));
+                    mHandler.sendEmptyMessage(MSG_ON_READY);
                 }
 
                 @Override
                 public void getVersion(RemoteCallback remoteCallback) {
                     Objects.requireNonNull(remoteCallback);
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceIntelligenceService::onGetVersion,
-                                    OnDeviceIntelligenceService.this, l -> {
-                                        Bundle b = new Bundle();
-                                        b.putLong(
-                                                OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY,
-                                                l);
-                                        remoteCallback.sendResult(b);
-                                    }));
+                    Message msg = Message.obtain(mHandler, MSG_GET_VERSION,
+                            (LongConsumer) (l -> {
+                                Bundle b = new Bundle();
+                                b.putLong(OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY, l);
+                                remoteCallback.sendResult(b);
+                            }));
+                    mHandler.sendMessage(msg);
                 }
 
                 @Override
                 public void listFeatures(int callerUid,
                         IListFeaturesCallback listFeaturesCallback) {
                     Objects.requireNonNull(listFeaturesCallback);
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceIntelligenceService::onListFeatures,
-                                    OnDeviceIntelligenceService.this, callerUid,
-                                    wrapListFeaturesCallback(listFeaturesCallback)));
+                    Message msg = Message.obtain(mHandler, MSG_LIST_FEATURES,
+                            callerUid, 0, wrapListFeaturesCallback(listFeaturesCallback));
+                    mHandler.sendMessage(msg);
                 }
 
                 @Override
                 public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) {
                     Objects.requireNonNull(featureCallback);
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceIntelligenceService::onGetFeature,
-                                    OnDeviceIntelligenceService.this, callerUid,
-                                    id, wrapFeatureCallback(featureCallback)));
+                    Message msg = Message.obtain(mHandler, MSG_GET_FEATURE,
+                            callerUid, id,
+                            new GetFeatureParams(wrapFeatureCallback(featureCallback)));
+                    mHandler.sendMessage(msg);
                 }
 
 
@@ -173,11 +228,11 @@
                         IFeatureDetailsCallback featureDetailsCallback) {
                     Objects.requireNonNull(feature);
                     Objects.requireNonNull(featureDetailsCallback);
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceIntelligenceService::onGetFeatureDetails,
-                                    OnDeviceIntelligenceService.this, callerUid,
-                                    feature, wrapFeatureDetailsCallback(featureDetailsCallback)));
+                    Message msg = Message.obtain(mHandler, MSG_GET_FEATURE_DETAILS,
+                            new FeatureDetailsParams(feature,
+                                    wrapFeatureDetailsCallback(featureDetailsCallback)));
+                    msg.arg1 = callerUid;
+                    mHandler.sendMessage(msg);
                 }
 
                 @Override
@@ -186,18 +241,24 @@
                         IDownloadCallback downloadCallback) {
                     Objects.requireNonNull(feature);
                     Objects.requireNonNull(downloadCallback);
-                    ICancellationSignal transport = null;
+
+                    CancellationSignal cancellationSignal = new CancellationSignal();
                     if (cancellationSignalFuture != null) {
-                        transport = CancellationSignal.createTransport();
+                        ICancellationSignal transport = new ICancellationSignal.Stub() {
+                            @Override
+                            public void cancel() {
+                                cancellationSignal.cancel();
+                            }
+                        };
                         cancellationSignalFuture.complete(transport);
                     }
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceIntelligenceService::onDownloadFeature,
-                                    OnDeviceIntelligenceService.this, callerUid,
-                                    feature,
-                                    CancellationSignal.fromTransport(transport),
+
+                    Message msg = Message.obtain(mHandler, MSG_DOWNLOAD_FEATURE,
+                            new DownloadParams(feature,
+                                    cancellationSignalFuture != null ? cancellationSignal : null,
                                     wrapDownloadCallback(downloadCallback)));
+                    msg.arg1 = callerUid;
+                    mHandler.sendMessage(msg);
                 }
 
                 @Override
@@ -205,11 +266,9 @@
                         AndroidFuture<ParcelFileDescriptor> future) {
                     Objects.requireNonNull(fileName);
                     Objects.requireNonNull(future);
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceIntelligenceService::onGetReadOnlyFileDescriptor,
-                                    OnDeviceIntelligenceService.this, fileName,
-                                    future));
+                    Message msg = Message.obtain(mHandler, MSG_GET_READ_ONLY_FILE_DESCRIPTOR,
+                            new FileDescriptorParams(fileName, future));
+                    mHandler.sendMessage(msg);
                 }
 
                 @Override
@@ -217,16 +276,15 @@
                         Feature feature, RemoteCallback remoteCallback) {
                     Objects.requireNonNull(feature);
                     Objects.requireNonNull(remoteCallback);
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceIntelligenceService::onGetReadOnlyFeatureFileDescriptorMap,
-                                    OnDeviceIntelligenceService.this, feature,
-                                    parcelFileDescriptorMap -> {
-                                        Bundle bundle = new Bundle();
-                                        parcelFileDescriptorMap.forEach(bundle::putParcelable);
-                                        remoteCallback.sendResult(bundle);
-                                        tryClosePfds(parcelFileDescriptorMap.values());
-                                    }));
+                    Message msg = Message.obtain(mHandler,
+                            MSG_GET_READ_ONLY_FEATURE_FILE_DESCRIPTOR_MAP,
+                            new FeatureFileDescriptorParams(feature, parcelFileDescriptorMap -> {
+                                Bundle bundle = new Bundle();
+                                parcelFileDescriptorMap.forEach(bundle::putParcelable);
+                                remoteCallback.sendResult(bundle);
+                                tryClosePfds(parcelFileDescriptorMap.values());
+                            }));
+                    mHandler.sendMessage(msg);
                 }
 
                 @Override
@@ -237,18 +295,12 @@
 
                 @Override
                 public void notifyInferenceServiceConnected() {
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceIntelligenceService::onInferenceServiceConnected,
-                                    OnDeviceIntelligenceService.this));
+                    mHandler.sendEmptyMessage(MSG_INFERENCE_SERVICE_CONNECTED);
                 }
 
                 @Override
                 public void notifyInferenceServiceDisconnected() {
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceIntelligenceService::onInferenceServiceDisconnected,
-                                    OnDeviceIntelligenceService.this));
+                    mHandler.sendEmptyMessage(MSG_INFERENCE_SERVICE_DISCONNECTED);
                 }
             };
         }
@@ -257,13 +309,77 @@
     }
 
     /**
+     * The {@link Intent} that must be declared as handled by the service. To be supported, the
+     * service must also require the
+     * {@link android.Manifest.permission#BIND_ON_DEVICE_INTELLIGENCE_SERVICE}
+     * permission so that other applications can not abuse it.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE =
+            "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
+
+    // Parameter holder classes
+    private static class GetFeatureParams {
+        final OutcomeReceiver<Feature, OnDeviceIntelligenceException> callback;
+
+        GetFeatureParams(OutcomeReceiver<Feature, OnDeviceIntelligenceException> callback) {
+            this.callback = callback;
+        }
+    }
+
+    private static class FeatureDetailsParams {
+        final Feature feature;
+        final OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> callback;
+
+        FeatureDetailsParams(Feature feature,
+                OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> callback) {
+            this.feature = feature;
+            this.callback = callback;
+        }
+    }
+
+    private static class DownloadParams {
+        final Feature feature;
+        final CancellationSignal cancellationSignal;
+        final DownloadCallback callback;
+
+        DownloadParams(Feature feature, CancellationSignal cancellationSignal,
+                DownloadCallback callback) {
+            this.feature = feature;
+            this.cancellationSignal = cancellationSignal;
+            this.callback = callback;
+        }
+    }
+
+    private static class FileDescriptorParams {
+        final String fileName;
+        final AndroidFuture<ParcelFileDescriptor> future;
+
+        FileDescriptorParams(String fileName, AndroidFuture<ParcelFileDescriptor> future) {
+            this.fileName = fileName;
+            this.future = future;
+        }
+    }
+
+    private static class FeatureFileDescriptorParams {
+        final Feature feature;
+        final Consumer<Map<String, ParcelFileDescriptor>> consumer;
+
+        FeatureFileDescriptorParams(Feature feature,
+                Consumer<Map<String, ParcelFileDescriptor>> consumer) {
+            this.feature = feature;
+            this.consumer = consumer;
+        }
+    }
+
+    /**
      * Using this signal to assertively a signal each time service binds successfully, used only in
      * tests to get a signal that service instance is ready. This is needed because we cannot rely
      * on {@link #onCreate} or {@link #onBind} to be invoke on each binding.
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public void onReady() {
     }
 
@@ -306,7 +422,7 @@
                     new IProcessingUpdateStatusCallback.Stub() {
                         @Override
                         public void onSuccess(PersistableBundle result) {
-                            Binder.withCleanCallingIdentity(() -> {
+                            BinderUtils.withCleanCallingIdentity(() -> {
                                 callbackExecutor.execute(
                                         () -> statusReceiver.onResult(result));
                             });
@@ -314,7 +430,7 @@
 
                         @Override
                         public void onFailure(int errorCode, String errorMessage) {
-                            Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+                            BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
                                     () -> statusReceiver.onError(
                                             new OnDeviceIntelligenceException(
                                                     errorCode, errorMessage))));
@@ -459,7 +575,7 @@
     private void onGetReadOnlyFileDescriptor(@NonNull String fileName,
             @NonNull AndroidFuture<ParcelFileDescriptor> future) {
         Slog.v(TAG, "onGetReadOnlyFileDescriptor " + fileName);
-        Binder.withCleanCallingIdentity(() -> {
+        BinderUtils.withCleanCallingIdentity(() -> {
             Slog.v(TAG,
                     "onGetReadOnlyFileDescriptor: " + fileName + " under internal app storage.");
             File f = new File(getBaseContext().getFilesDir(), fileName);
@@ -476,7 +592,11 @@
             } finally {
                 future.complete(pfd);
                 if (pfd != null) {
-                    pfd.close();
+                    try {
+                        pfd.close();
+                    } catch (IOException e) {
+                        Log.w(TAG, "Error closing FD", e);
+                    }
                 }
             }
         });
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
similarity index 74%
rename from core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 3181556..315dbaf 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -19,10 +19,8 @@
 import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.AUGMENT_REQUEST_CONTENT_BUNDLE_KEY;
 import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
 
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
-import android.annotation.CallbackExecutor;
 import android.annotation.CallSuper;
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,7 +29,9 @@
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.ICancellationSignal;
 import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.IRemoteCallback;
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
 import android.app.ondeviceintelligence.ITokenInfoCallback;
@@ -48,11 +48,9 @@
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
-import android.os.ICancellationSignal;
-import android.os.IRemoteCallback;
 import android.os.Looper;
+import android.os.Message;
 import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
@@ -61,7 +59,8 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
+import com.android.modules.utils.HandlerExecutor;
 
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -100,6 +99,12 @@
 public abstract class OnDeviceSandboxedInferenceService extends Service {
     private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName();
 
+    private static final int MSG_TOKEN_INFO_REQUEST = 1;
+    private static final int MSG_PROCESS_REQUEST_STREAMING = 2;
+    private static final int MSG_PROCESS_REQUEST = 3;
+    private static final int MSG_UPDATE_PROCESSING_STATE = 4;
+
+
     /**
      * @hide
      */
@@ -133,12 +138,12 @@
      * @hide
      */
     public static final String MODEL_LOADED_BROADCAST_INTENT =
-        "android.service.ondeviceintelligence.MODEL_LOADED";
+            "android.service.ondeviceintelligence.MODEL_LOADED";
     /**
      * @hide
      */
     public static final String MODEL_UNLOADED_BROADCAST_INTENT =
-        "android.service.ondeviceintelligence.MODEL_UNLOADED";
+            "android.service.ondeviceintelligence.MODEL_UNLOADED";
 
     /**
      * @hide
@@ -152,12 +157,115 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */);
+        mHandler = new Handler(Looper.getMainLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_TOKEN_INFO_REQUEST:
+                        TokenInfoParams params = (TokenInfoParams) msg.obj;
+                        OnDeviceSandboxedInferenceService.this.onTokenInfoRequest(
+                                msg.arg1,
+                                params.feature,
+                                params.request,
+                                params.cancellationSignal,
+                                params.callback);
+                        break;
+                    case MSG_PROCESS_REQUEST_STREAMING:
+                        StreamingRequestParams streamParams = (StreamingRequestParams) msg.obj;
+                        OnDeviceSandboxedInferenceService.this.onProcessRequestStreaming(
+                                msg.arg1,
+                                streamParams.feature,
+                                streamParams.request,
+                                msg.arg2,
+                                streamParams.cancellationSignal,
+                                streamParams.processingSignal,
+                                streamParams.callback);
+                        break;
+                    case MSG_PROCESS_REQUEST:
+                        RequestParams requestParams = (RequestParams) msg.obj;
+                        OnDeviceSandboxedInferenceService.this.onProcessRequest(
+                                msg.arg1,
+                                requestParams.feature,
+                                requestParams.request,
+                                msg.arg2,
+                                requestParams.cancellationSignal,
+                                requestParams.processingSignal,
+                                requestParams.callback);
+                        break;
+                    case MSG_UPDATE_PROCESSING_STATE:
+                        UpdateStateParams stateParams = (UpdateStateParams) msg.obj;
+                        OnDeviceSandboxedInferenceService.this.onUpdateProcessingState(
+                                stateParams.processingState,
+                                stateParams.callback);
+                        break;
+                }
+            }
+        };
     }
 
-    /**
-     * @hide
-     */
+    // Parameter holder classes
+    private static class TokenInfoParams {
+        final Feature feature;
+        final Bundle request;
+        final CancellationSignal cancellationSignal;
+        final OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback;
+
+        TokenInfoParams(Feature feature, Bundle request, CancellationSignal cancellationSignal,
+                OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback) {
+            this.feature = feature;
+            this.request = request;
+            this.cancellationSignal = cancellationSignal;
+            this.callback = callback;
+        }
+    }
+
+    private static class StreamingRequestParams {
+        final Feature feature;
+        final Bundle request;
+        final CancellationSignal cancellationSignal;
+        final ProcessingSignal processingSignal;
+        final StreamingProcessingCallback callback;
+
+        StreamingRequestParams(Feature feature, Bundle request,
+                CancellationSignal cancellationSignal, ProcessingSignal processingSignal,
+                StreamingProcessingCallback callback) {
+            this.feature = feature;
+            this.request = request;
+            this.cancellationSignal = cancellationSignal;
+            this.processingSignal = processingSignal;
+            this.callback = callback;
+        }
+    }
+
+    private static class RequestParams {
+        final Feature feature;
+        final Bundle request;
+        final CancellationSignal cancellationSignal;
+        final ProcessingSignal processingSignal;
+        final ProcessingCallback callback;
+
+        RequestParams(Feature feature, Bundle request,
+                CancellationSignal cancellationSignal, ProcessingSignal processingSignal,
+                ProcessingCallback callback) {
+            this.feature = feature;
+            this.request = request;
+            this.cancellationSignal = cancellationSignal;
+            this.processingSignal = processingSignal;
+            this.callback = callback;
+        }
+    }
+
+    private static class UpdateStateParams {
+        final Bundle processingState;
+        final OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> callback;
+
+        UpdateStateParams(Bundle processingState,
+                OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> callback) {
+            this.processingState = processingState;
+            this.callback = callback;
+        }
+    }
+
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
@@ -168,8 +276,7 @@
                         IRemoteCallback remoteCallback) throws RemoteException {
                     Objects.requireNonNull(storageService);
                     mRemoteStorageService = storageService;
-                    remoteCallback.sendResult(
-                            Bundle.EMPTY); //to notify caller uid to system-server.
+                    remoteCallback.sendResult(Bundle.EMPTY);
                 }
 
                 @Override
@@ -178,34 +285,42 @@
                         ITokenInfoCallback tokenInfoCallback) {
                     Objects.requireNonNull(feature);
                     Objects.requireNonNull(tokenInfoCallback);
-                    ICancellationSignal transport = null;
+                    CancellationSignal cancellationSignal = new CancellationSignal();
                     if (cancellationSignalFuture != null) {
-                        transport = CancellationSignal.createTransport();
+                        ICancellationSignal transport = new ICancellationSignal.Stub() {
+                            @Override
+                            public void cancel() {
+                                cancellationSignal.cancel();
+                            }
+                        };
                         cancellationSignalFuture.complete(transport);
                     }
 
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceSandboxedInferenceService::onTokenInfoRequest,
-                                    OnDeviceSandboxedInferenceService.this,
-                                    callerUid, feature,
-                                    request,
-                                    CancellationSignal.fromTransport(transport),
+                    Message msg = Message.obtain(mHandler, MSG_TOKEN_INFO_REQUEST,
+                            callerUid, 0,
+                            new TokenInfoParams(feature, request,
+                                    cancellationSignalFuture != null ? cancellationSignal : null,
                                     wrapTokenInfoCallback(tokenInfoCallback)));
+                    mHandler.sendMessage(msg);
                 }
 
                 @Override
-                public void processRequestStreaming(int callerUid, Feature feature, Bundle request,
-                        int requestType,
+                public void processRequestStreaming(int callerUid, Feature feature,
+                        Bundle request, int requestType,
                         AndroidFuture cancellationSignalFuture,
                         AndroidFuture processingSignalFuture,
                         IStreamingResponseCallback callback) {
                     Objects.requireNonNull(feature);
                     Objects.requireNonNull(callback);
 
-                    ICancellationSignal transport = null;
+                    CancellationSignal cancellationSignal = new CancellationSignal();
                     if (cancellationSignalFuture != null) {
-                        transport = CancellationSignal.createTransport();
+                        ICancellationSignal transport = new ICancellationSignal.Stub() {
+                            @Override
+                            public void cancel() {
+                                cancellationSignal.cancel();
+                            }
+                        };
                         cancellationSignalFuture.complete(transport);
                     }
                     IProcessingSignal processingSignalTransport = null;
@@ -214,30 +329,32 @@
                         processingSignalFuture.complete(processingSignalTransport);
                     }
 
-
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceSandboxedInferenceService::onProcessRequestStreaming,
-                                    OnDeviceSandboxedInferenceService.this, callerUid,
-                                    feature,
-                                    request,
-                                    requestType,
-                                    CancellationSignal.fromTransport(transport),
+                    Message msg = Message.obtain(mHandler, MSG_PROCESS_REQUEST_STREAMING,
+                            callerUid, requestType,
+                            new StreamingRequestParams(feature, request,
+                                    cancellationSignalFuture != null ? cancellationSignal : null,
                                     ProcessingSignal.fromTransport(processingSignalTransport),
                                     wrapStreamingResponseCallback(callback)));
+                    mHandler.sendMessage(msg);
                 }
 
                 @Override
-                public void processRequest(int callerUid, Feature feature, Bundle request,
-                        int requestType,
+                public void processRequest(int callerUid, Feature feature,
+                        Bundle request, int requestType,
                         AndroidFuture cancellationSignalFuture,
                         AndroidFuture processingSignalFuture,
                         IResponseCallback callback) {
                     Objects.requireNonNull(feature);
                     Objects.requireNonNull(callback);
-                    ICancellationSignal transport = null;
+
+                    CancellationSignal cancellationSignal = new CancellationSignal();
                     if (cancellationSignalFuture != null) {
-                        transport = CancellationSignal.createTransport();
+                        ICancellationSignal transport = new ICancellationSignal.Stub() {
+                            @Override
+                            public void cancel() {
+                                cancellationSignal.cancel();
+                            }
+                        };
                         cancellationSignalFuture.complete(transport);
                     }
                     IProcessingSignal processingSignalTransport = null;
@@ -245,14 +362,14 @@
                         processingSignalTransport = ProcessingSignal.createTransport();
                         processingSignalFuture.complete(processingSignalTransport);
                     }
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceSandboxedInferenceService::onProcessRequest,
-                                    OnDeviceSandboxedInferenceService.this, callerUid, feature,
-                                    request, requestType,
-                                    CancellationSignal.fromTransport(transport),
+
+                    Message msg = Message.obtain(mHandler, MSG_PROCESS_REQUEST,
+                            callerUid, requestType,
+                            new RequestParams(feature, request,
+                                    cancellationSignalFuture != null ? cancellationSignal : null,
                                     ProcessingSignal.fromTransport(processingSignalTransport),
                                     wrapResponseCallback(callback)));
+                    mHandler.sendMessage(msg);
                 }
 
                 @Override
@@ -260,11 +377,11 @@
                         IProcessingUpdateStatusCallback callback) {
                     Objects.requireNonNull(processingState);
                     Objects.requireNonNull(callback);
-                    mHandler.executeOrSendMessage(
-                            obtainMessage(
-                                    OnDeviceSandboxedInferenceService::onUpdateProcessingState,
-                                    OnDeviceSandboxedInferenceService.this, processingState,
+
+                    Message msg = Message.obtain(mHandler, MSG_UPDATE_PROCESSING_STATE,
+                            new UpdateStateParams(processingState,
                                     wrapOutcomeReceiver(callback)));
+                    mHandler.sendMessage(msg);
                 }
             };
         }
@@ -471,7 +588,7 @@
             IResponseCallback callback) {
         return new ProcessingCallback() {
             @Override
-            public void onResult(@androidx.annotation.NonNull Bundle result) {
+            public void onResult(@NonNull Bundle result) {
                 try {
                     callback.onSuccess(result);
                 } catch (RemoteException e) {
@@ -507,7 +624,7 @@
             IStreamingResponseCallback callback) {
         return new StreamingProcessingCallback() {
             @Override
-            public void onPartialResult(@androidx.annotation.NonNull Bundle partialResult) {
+            public void onPartialResult(@NonNull Bundle partialResult) {
                 try {
                     callback.onNewContent(partialResult);
                 } catch (RemoteException e) {
@@ -516,7 +633,7 @@
             }
 
             @Override
-            public void onResult(@androidx.annotation.NonNull Bundle result) {
+            public void onResult(@NonNull Bundle result) {
                 try {
                     callback.onSuccess(result);
                 } catch (RemoteException e) {
@@ -549,7 +666,7 @@
     }
 
     private RemoteCallback wrapRemoteCallback(
-            @androidx.annotation.NonNull Consumer<Bundle> contentCallback) {
+            @NonNull Consumer<Bundle> contentCallback) {
         return new RemoteCallback(
                 result -> {
                     if (result != null) {
@@ -604,7 +721,7 @@
 
             @Override
             public void onError(
-                    @androidx.annotation.NonNull OnDeviceIntelligenceException error) {
+                    @NonNull OnDeviceIntelligenceException error) {
                 try {
                     callback.onFailure(error.getErrorCode(), error.getMessage());
                 } catch (RemoteException e) {
diff --git a/packages/NeuralNetworks/service/Android.bp b/packages/NeuralNetworks/service/Android.bp
new file mode 100644
index 0000000..05c603f
--- /dev/null
+++ b/packages/NeuralNetworks/service/Android.bp
@@ -0,0 +1,29 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "service-ondeviceintelligence-sources",
+    srcs: [
+        "java/**/*.java",
+    ],
+    path: "java",
+    visibility: [
+        "//frameworks/base:__subpackages__",
+        "//packages/modules/NeuralNetworks:__subpackages__",
+    ],
+}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java
similarity index 95%
rename from services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java
index 7dd8f2f..53ef9e8 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java
@@ -42,7 +42,7 @@
 import android.system.Os;
 import android.util.Log;
 
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
 
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeoutException;
@@ -50,6 +50,8 @@
 /**
  * Util methods for ensuring the Bundle passed in various methods are read-only and restricted to
  * some known types.
+ *
+ * @hide
  */
 public class BundleUtil {
     private static final String TAG = "BundleUtil";
@@ -76,7 +78,7 @@
                  * {@link ClassNotFoundException} exception is swallowed and `null` is returned
                  * instead. We want to ensure cleanup of null entries in such case.
                  */
-                bundle.putObject(key, null);
+                bundle.putParcelable(key, null);
                 continue;
             }
             if (canMarshall(obj) || obj instanceof CursorWindow) {
@@ -122,7 +124,7 @@
                  * {@link ClassNotFoundException} exception is swallowed and `null` is returned
                  * instead. We want to ensure cleanup of null entries in such case.
                  */
-                bundle.putObject(key, null);
+                bundle.putParcelable(key, null);
                 continue;
             }
             if (canMarshall(obj)) {
@@ -167,7 +169,7 @@
                  * {@link ClassNotFoundException} exception is swallowed and `null` is returned
                  * instead. We want to ensure cleanup of null entries in such case.
                  */
-                bundle.putObject(key, null);
+                bundle.putParcelable(key, null);
                 continue;
             }
             if (canMarshall(obj)) {
@@ -317,9 +319,13 @@
         };
     }
 
-    private static boolean canMarshall(Object obj) {
-        return obj instanceof byte[] || obj instanceof PersistableBundle
-                || PersistableBundle.isValidType(obj);
+    private static boolean canMarshall(Object value) {
+        return (value instanceof byte[]) || (value instanceof Integer) || (value instanceof Long) ||
+                (value instanceof Double) || (value instanceof String) ||
+                (value instanceof int[]) || (value instanceof long[]) ||
+                (value instanceof double[]) || (value instanceof String[]) ||
+                (value instanceof PersistableBundle) || (value == null) ||
+                (value instanceof Boolean) || (value instanceof boolean[]);
     }
 
     private static void ensureValidBundle(Bundle bundle) {
@@ -364,7 +370,7 @@
             }
         } catch (ErrnoException e) {
             throw new BadParcelableException(
-                    "Invalid File descriptor passed in the Bundle.", e);
+                    "Invalid File descriptor passed in the Bundle.");
         }
     }
 
diff --git a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
similarity index 99%
rename from services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
index bef3f80..e8a1b322 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
@@ -28,6 +28,9 @@
 import java.util.List;
 import java.util.TreeSet;
 
+/**
+ * @hide
+ */
 public class InferenceInfoStore {
     private static final String TAG = "InferenceInfoStore";
     private final TreeSet<InferenceInfo> inferenceInfos;
@@ -98,4 +101,4 @@
                 info.startTimeMs).setEndTimeMillis(info.endTimeMs).setSuspendedTimeMillis(
                 info.suspendedTimeMs).build();
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
similarity index 60%
rename from services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
index 1450dc0..6badc53 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
@@ -16,7 +16,21 @@
 
 package com.android.server.ondeviceintelligence;
 
-public interface OnDeviceIntelligenceManagerInternal {
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+
+/**
+ * Exposes APIs to {@code system_server} components outside of the module boundaries.
+ * <p> This API should be access using {@link com.android.server.LocalManagerRegistry}. </p>
+ *
+ * @hide
+ */
+@SystemApi(client = Client.SYSTEM_SERVER)
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
+public interface OnDeviceIntelligenceManagerLocal {
     /**
      * Gets the uid for the process that is currently hosting the
      * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} registered on
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
similarity index 84%
rename from services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index b0d69e6..607ec1c 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,38 +16,42 @@
 
 package com.android.server.ondeviceintelligence;
 
+import static android.app.ondeviceintelligence.flags.Flags.enableOnDeviceIntelligenceModule;
+
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS;
 import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.DEVICE_CONFIG_UPDATE_BUNDLE_KEY;
-import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY;
-import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY;
 import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BROADCAST_INTENT;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY;
 import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BROADCAST_INTENT;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY;
 import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY;
 
 import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams;
-import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly;
 import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams;
+import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly;
 import static com.android.server.ondeviceintelligence.BundleUtil.wrapWithValidation;
 
-
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.app.AppGlobals;
 import android.app.ondeviceintelligence.DownloadCallback;
 import android.app.ondeviceintelligence.Feature;
 import android.app.ondeviceintelligence.FeatureDetails;
+import android.app.ondeviceintelligence.ICancellationSignal;
 import android.app.ondeviceintelligence.IDownloadCallback;
 import android.app.ondeviceintelligence.IFeatureCallback;
 import android.app.ondeviceintelligence.IFeatureDetailsCallback;
 import android.app.ondeviceintelligence.IListFeaturesCallback;
 import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
 import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.IRemoteCallback;
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
 import android.app.ondeviceintelligence.ITokenInfoCallback;
 import android.app.ondeviceintelligence.InferenceInfo;
 import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
+import android.app.ondeviceintelligence.utils.BinderUtils;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -58,16 +62,12 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.ICancellationSignal;
-import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -82,17 +82,14 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.ServiceConnector;
-import com.android.internal.os.BackgroundThread;
-import com.android.server.LocalServices;
+import com.android.modules.utils.AndroidFuture;
+import com.android.modules.utils.BackgroundThread;
+import com.android.modules.utils.ServiceConnector;
+import com.android.server.LocalManagerRegistry;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
 import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback;
 
-import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.List;
 import java.util.Objects;
@@ -182,9 +179,11 @@
     public void onStart() {
         publishBinderService(
                 Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(),
-                /* allowIsolated = */true);
-        LocalServices.addService(OnDeviceIntelligenceManagerInternal.class,
-                this::getRemoteInferenceServiceUid);
+                /* allowIsolated = */ true);
+        if (enableOnDeviceIntelligenceModule()) {
+            LocalManagerRegistry.addManager(OnDeviceIntelligenceManagerLocal.class,
+                    this::getRemoteInferenceServiceUid);
+        }
     }
 
     @Override
@@ -203,10 +202,10 @@
     public void onUserUnlocked(@NonNull TargetUser user) {
         Slog.d(TAG, "onUserUnlocked: " + user.getUserHandle());
         //connect to remote services(if available) during boot.
-        if(user.getUserHandle().equals(UserHandle.SYSTEM)) {
+        if (user.getUserHandle().equals(UserHandle.SYSTEM)) {
             try {
-                ensureRemoteInferenceServiceInitialized();
-                ensureRemoteIntelligenceServiceInitialized();
+                ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ false);
+                ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ false);
             } catch (Exception e) {
                 Slog.w(TAG, "Couldn't pre-start remote ondeviceintelligence services.", e);
             }
@@ -251,7 +250,7 @@
                     remoteCallback.sendResult(null);
                     return;
                 }
-                ensureRemoteIntelligenceServiceInitialized();
+                ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
                 mRemoteOnDeviceIntelligenceService.postAsync(
                         service -> {
                             AndroidFuture future = new AndroidFuture();
@@ -279,7 +278,7 @@
                             PersistableBundle.EMPTY);
                     return;
                 }
-                ensureRemoteIntelligenceServiceInitialized();
+                ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
                 int callerUid = Binder.getCallingUid();
                 mRemoteOnDeviceIntelligenceService.postAsync(
                         service -> {
@@ -317,7 +316,7 @@
                             PersistableBundle.EMPTY);
                     return;
                 }
-                ensureRemoteIntelligenceServiceInitialized();
+                ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
                 int callerUid = Binder.getCallingUid();
                 mRemoteOnDeviceIntelligenceService.postAsync(
                         service -> {
@@ -361,7 +360,7 @@
                             PersistableBundle.EMPTY);
                     return;
                 }
-                ensureRemoteIntelligenceServiceInitialized();
+                ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
                 int callerUid = Binder.getCallingUid();
                 mRemoteOnDeviceIntelligenceService.postAsync(
                         service -> {
@@ -404,7 +403,7 @@
                             "OnDeviceIntelligenceManagerService is unavailable",
                             PersistableBundle.EMPTY);
                 }
-                ensureRemoteIntelligenceServiceInitialized();
+                ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
                 int callerUid = Binder.getCallingUid();
                 mRemoteOnDeviceIntelligenceService.postAsync(
                         service -> {
@@ -444,7 +443,7 @@
                                 "OnDeviceIntelligenceManagerService is unavailable",
                                 PersistableBundle.EMPTY);
                     }
-                    ensureRemoteInferenceServiceInitialized();
+                    ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true);
                     int callerUid = Binder.getCallingUid();
                     result = mRemoteInferenceService.postAsync(
                             service -> {
@@ -488,7 +487,7 @@
                                 "OnDeviceIntelligenceManagerService is unavailable",
                                 PersistableBundle.EMPTY);
                     }
-                    ensureRemoteInferenceServiceInitialized();
+                    ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true);
                     int callerUid = Binder.getCallingUid();
                     result = mRemoteInferenceService.postAsync(
                             service -> {
@@ -534,7 +533,7 @@
                                 "OnDeviceIntelligenceManagerService is unavailable",
                                 PersistableBundle.EMPTY);
                     }
-                    ensureRemoteInferenceServiceInitialized();
+                    ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true);
                     int callerUid = Binder.getCallingUid();
                     result = mRemoteInferenceService.postAsync(
                             service -> {
@@ -559,20 +558,31 @@
             }
 
             @Override
-            public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
-                    String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
-                new OnDeviceIntelligenceShellCommand(OnDeviceIntelligenceManagerService.this).exec(
-                        this, in, out, err, args, callback, resultReceiver);
+            public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+                    @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+                    @NonNull String[] args) {
+                return new com.android.server.ondeviceintelligence.OnDeviceIntelligenceShellCommand(
+                        OnDeviceIntelligenceManagerService.this).exec(
+                        this,
+                        in.getFileDescriptor(),
+                        out.getFileDescriptor(),
+                        err.getFileDescriptor(),
+                        args);
             }
         };
     }
 
-    private void ensureRemoteIntelligenceServiceInitialized() {
+    private boolean ensureRemoteIntelligenceServiceInitialized(boolean throwIfServiceInvalid) {
         synchronized (mLock) {
             if (mRemoteOnDeviceIntelligenceService == null) {
                 String serviceName = getServiceNames()[0];
-                Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, false));
-                mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
+                if (!BinderUtils.withCleanCallingIdentity(
+                        () -> validateServiceElevated(serviceName, false,
+                                throwIfServiceInvalid))) {
+                    return false;
+                }
+                mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(
+                        mContext,
                         ComponentName.unflattenFromString(serviceName),
                         UserHandle.SYSTEM.getIdentifier());
                 mRemoteOnDeviceIntelligenceService.setServiceLifecycleCallbacks(
@@ -591,6 +601,7 @@
                         });
             }
         }
+        return true;
     }
 
     @NonNull
@@ -604,13 +615,21 @@
                     AndroidFuture<Void> result = null;
                     try {
                         sanitizeStateParams(processingState);
-                        ensureRemoteInferenceServiceInitialized();
-                        result = mRemoteInferenceService.post(
-                                service -> service.updateProcessingState(
-                                        processingState, callback));
-                        result.whenCompleteAsync(
-                                (c, e) -> BundleUtil.tryCloseResource(processingState),
-                                resourceClosingExecutor);
+                        if (ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */
+                                false)) {
+                            result = mRemoteInferenceService.post(
+                                    service -> service.updateProcessingState(
+                                            processingState, callback));
+                            result.whenCompleteAsync(
+                                    (c, e) -> BundleUtil.tryCloseResource(processingState),
+                                    resourceClosingExecutor);
+                        } else {
+                            callback.onFailure(
+                                    OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+                                    "Remote service cannot be initialized.");
+                        }
+                    } catch (RemoteException e) {
+                        Slog.w("Failed to invoke updateProcessingState", e);
                     } finally {
                         if (result == null) {
                             resourceClosingExecutor.execute(
@@ -622,11 +641,14 @@
         };
     }
 
-    private void ensureRemoteInferenceServiceInitialized() {
+    private boolean ensureRemoteInferenceServiceInitialized(boolean throwIfServiceInvalid) {
         synchronized (mLock) {
             if (mRemoteInferenceService == null) {
                 String serviceName = getServiceNames()[1];
-                Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, true));
+                if (!BinderUtils.withCleanCallingIdentity(
+                        () -> validateServiceElevated(serviceName, true, throwIfServiceInvalid))) {
+                    return false;
+                }
                 mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
                         ComponentName.unflattenFromString(serviceName),
                         UserHandle.SYSTEM.getIdentifier());
@@ -636,7 +658,11 @@
                             public void onConnected(
                                     @NonNull IOnDeviceSandboxedInferenceService service) {
                                 try {
-                                    ensureRemoteIntelligenceServiceInitialized();
+                                    if (!ensureRemoteIntelligenceServiceInitialized(
+                                            /* throwServiceIfInvalid */
+                                            false)) {
+                                        return;
+                                    }
                                     service.registerRemoteStorageService(
                                             getIRemoteStorageService(), new IRemoteCallback.Stub() {
                                                 @Override
@@ -659,20 +685,29 @@
                             @Override
                             public void onDisconnected(
                                     @NonNull IOnDeviceSandboxedInferenceService service) {
-                                ensureRemoteIntelligenceServiceInitialized();
+                                if (!ensureRemoteIntelligenceServiceInitialized(
+                                        /* throwServiceIfInvalid */
+                                        false)) {
+                                    return;
+                                }
                                 mRemoteOnDeviceIntelligenceService.run(
                                         IOnDeviceIntelligenceService::notifyInferenceServiceDisconnected);
                             }
 
                             @Override
                             public void onBinderDied() {
-                                ensureRemoteIntelligenceServiceInitialized();
+                                if (!ensureRemoteIntelligenceServiceInitialized(
+                                        /* throwServiceIfInvalid */
+                                        false)) {
+                                    return;
+                                }
                                 mRemoteOnDeviceIntelligenceService.run(
                                         IOnDeviceIntelligenceService::notifyInferenceServiceDisconnected);
                             }
                         });
             }
         }
+        return true;
     }
 
     private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) {
@@ -743,9 +778,13 @@
             if (mTemporaryConfigNamespace != null) {
                 return mTemporaryConfigNamespace;
             }
-
-            return mContext.getResources().getString(
-                    R.string.config_defaultOnDeviceIntelligenceDeviceConfigNamespace);
+            return mContext.getResources()
+                    .getString(
+                            mContext.getResources()
+                                    .getIdentifier(
+                                            "config_defaultOnDeviceIntelligenceDeviceConfigNamespace",
+                                            "string",
+                                            "android"));
         }
     }
 
@@ -759,7 +798,11 @@
         }
         Bundle bundle = new Bundle();
         bundle.putParcelable(DEVICE_CONFIG_UPDATE_BUNDLE_KEY, persistableBundle);
-        ensureRemoteInferenceServiceInitialized();
+        if (!ensureRemoteIntelligenceServiceInitialized(
+                /* throwServiceIfInvalid */
+                false)) {
+            return;
+        }
         mRemoteInferenceService.run(service -> service.updateProcessingState(bundle,
                 new IProcessingUpdateStatusCallback.Stub() {
                     @Override
@@ -782,7 +825,13 @@
             public void getReadOnlyFileDescriptor(
                     String filePath,
                     AndroidFuture<ParcelFileDescriptor> future) {
-                ensureRemoteIntelligenceServiceInitialized();
+                if (!ensureRemoteIntelligenceServiceInitialized(
+                        /* throwServiceIfInvalid */
+                        false)) {
+                    future.completeExceptionally(new OnDeviceIntelligenceException(
+                            OnDeviceIntelligenceException.PROCESSING_ERROR_NOT_AVAILABLE));
+                    return;
+                }
                 AndroidFuture<ParcelFileDescriptor> pfdFuture = new AndroidFuture<>();
                 mRemoteOnDeviceIntelligenceService.run(
                         service -> service.getReadOnlyFileDescriptor(
@@ -805,7 +854,7 @@
             public void getReadOnlyFeatureFileDescriptorMap(
                     Feature feature,
                     RemoteCallback remoteCallback) {
-                ensureRemoteIntelligenceServiceInitialized();
+                ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
                 mRemoteOnDeviceIntelligenceService.run(
                         service -> service.getReadOnlyFeatureFileDescriptorMap(
                                 feature,
@@ -829,40 +878,48 @@
         };
     }
 
-    private void validateServiceElevated(String serviceName, boolean checkIsolated) {
+    private boolean validateServiceElevated(String serviceName, boolean checkIsolated,
+            boolean throwIfServiceInvalid) {
         try {
             if (TextUtils.isEmpty(serviceName)) {
-                throw new IllegalStateException(
-                        "Remote service is not configured to complete the request");
+                if (throwIfServiceInvalid) {
+                    throw new IllegalStateException(
+                            "Remote service is not configured to complete the request");
+                }
+                return false;
             }
             ComponentName serviceComponent = ComponentName.unflattenFromString(
                     serviceName);
-            ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+            ServiceInfo serviceInfo = mContext.getPackageManager().getServiceInfo(
                     serviceComponent,
                     PackageManager.MATCH_DIRECT_BOOT_AWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                    UserHandle.SYSTEM.getIdentifier());
-            if (serviceInfo != null) {
-                if (!checkIsolated) {
-                    checkServiceRequiresPermission(serviceInfo,
-                            Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE);
-                    return;
-                }
-
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+            if (!checkIsolated) {
                 checkServiceRequiresPermission(serviceInfo,
-                        Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
-                if (!isIsolatedService(serviceInfo)) {
-                    throw new SecurityException(
-                            "Call required an isolated service, but the configured service: "
-                                    + serviceName + ", is not isolated");
-                }
-            } else {
+                        Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE);
+                return true;
+            }
+
+            checkServiceRequiresPermission(serviceInfo,
+                    Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
+            if (!isIsolatedService(serviceInfo)) {
+                throw new SecurityException(
+                        "Call required an isolated service, but the configured service: "
+                                + serviceName + ", is not isolated");
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            if (throwIfServiceInvalid) {
                 throw new IllegalStateException(
                         "Remote service is not configured to complete the request.");
             }
-        } catch (RemoteException e) {
-            throw new IllegalStateException("Could not fetch service info for remote services", e);
+            return false;
+        } catch (SecurityException e) {
+            if (throwIfServiceInvalid) {
+                throw e;
+            }
+            return false;
         }
+        return true;
     }
 
     private static void checkServiceRequiresPermission(ServiceInfo serviceInfo,
@@ -870,8 +927,8 @@
         final String permission = serviceInfo.permission;
         if (!requiredPermission.equals(permission)) {
             throw new SecurityException(String.format(
-                    "Service %s requires %s permission. Found %s permission",
-                    serviceInfo.getComponentName(),
+                    "%s requires %s permission. Found %s permission",
+                    serviceInfo,
                     requiredPermission,
                     serviceInfo.permission));
         }
@@ -909,10 +966,22 @@
                 return mTemporaryServiceNames;
             }
         }
-        return new String[]{mContext.getResources().getString(
-                R.string.config_defaultOnDeviceIntelligenceService),
-                mContext.getResources().getString(
-                        R.string.config_defaultOnDeviceSandboxedInferenceService)};
+        return new String[]{
+                mContext.getResources()
+                        .getString(
+                        mContext.getResources()
+                                .getIdentifier(
+                                        "config_defaultOnDeviceIntelligenceService",
+                                        "string",
+                                        "android")),
+                mContext.getResources()
+                        .getString(
+                        mContext.getResources()
+                                .getIdentifier(
+                                        "config_defaultOnDeviceSandboxedInferenceService",
+                                        "string",
+                                        "android"))
+        };
     }
 
     protected String[] getBroadcastKeys() throws Resources.NotFoundException {
@@ -923,7 +992,7 @@
             }
         }
 
-        return new String[]{ MODEL_LOADED_BROADCAST_INTENT, MODEL_UNLOADED_BROADCAST_INTENT };
+        return new String[]{MODEL_LOADED_BROADCAST_INTENT, MODEL_UNLOADED_BROADCAST_INTENT};
     }
 
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
@@ -1068,7 +1137,7 @@
 
     private synchronized Handler getTemporaryHandler() {
         if (mTemporaryHandler == null) {
-            mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+            mTemporaryHandler = new Handler(Looper.getMainLooper()) {
                 @Override
                 public void handleMessage(Message msg) {
                     synchronized (mLock) {
@@ -1090,10 +1159,13 @@
         return mTemporaryHandler;
     }
 
+    // Using #getLong here as the timeout settings are only applicable to the services running in
+    // SYSTEM user only.
+    @SuppressWarnings("NonUserGetterCalled")
     private long getIdleTimeoutMs() {
-        return Settings.Secure.getLongForUser(mContext.getContentResolver(),
-                Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, TimeUnit.HOURS.toMillis(1),
-                mContext.getUserId());
+        return Settings.Secure.getLong(mContext.getContentResolver(),
+                ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS,
+                TimeUnit.HOURS.toMillis(1));
     }
 
     private int getRemoteInferenceServiceUid() {
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
similarity index 97%
rename from services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
index d2c84fa..c641de8 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -18,12 +18,16 @@
 
 import android.annotation.NonNull;
 import android.os.Binder;
-import android.os.ShellCommand;
+
+import com.android.modules.utils.BasicShellCommandHandler;
 
 import java.io.PrintWriter;
 import java.util.Objects;
 
-final class OnDeviceIntelligenceShellCommand extends ShellCommand {
+/**
+ * @hide
+ */
+final class OnDeviceIntelligenceShellCommand extends BasicShellCommandHandler {
     private static final String TAG = OnDeviceIntelligenceShellCommand.class.getSimpleName();
 
     @NonNull
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
similarity index 80%
rename from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
index ac9747a..0c43a30 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.ondeviceintelligence;
 
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS;
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
 import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
 
@@ -26,13 +27,15 @@
 import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
 import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
 
-import com.android.internal.infra.ServiceConnector;
+import com.android.modules.utils.ServiceConnector;
 
 import java.util.concurrent.TimeUnit;
 
 /**
  * Manages the connection to the remote on-device intelligence service. Also, handles unbinding
  * logic set by the service implementation via a Secure Settings flag.
+ *
+ * @hide
  */
 public class RemoteOnDeviceIntelligenceService extends
         ServiceConnector.Impl<IOnDeviceIntelligenceService> {
@@ -56,11 +59,13 @@
         return LONG_TIMEOUT;
     }
 
+    // Using #getLong here as the timeout settings are only applicable to the services running in
+    // SYSTEM user only.
     @Override
+    @SuppressWarnings("NonUserGetterCalled")
     protected long getAutoDisconnectTimeoutMs() {
-        return Settings.Secure.getLongForUser(mContext.getContentResolver(),
-                Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
-                TimeUnit.SECONDS.toMillis(30),
-                mContext.getUserId());
+        return Settings.Secure.getLong(mContext.getContentResolver(),
+                ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
+                TimeUnit.SECONDS.toMillis(30));
     }
 }
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
similarity index 82%
rename from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index 18b1383..8c5d5a7 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.ondeviceintelligence;
 
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS;
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
 import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
 
@@ -26,7 +27,7 @@
 import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
 import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
 
-import com.android.internal.infra.ServiceConnector;
+import com.android.modules.utils.ServiceConnector;
 
 import java.util.concurrent.TimeUnit;
 
@@ -35,6 +36,8 @@
  * Manages the connection to the remote on-device sand boxed inference service. Also, handles
  * unbinding
  * logic set by the service implementation via a SecureSettings flag.
+ *
+ * @hide
  */
 public class RemoteOnDeviceSandboxedInferenceService extends
         ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
@@ -65,12 +68,13 @@
         return LONG_TIMEOUT;
     }
 
-
+    // Using #getLong here as the timeout settings are only applicable to the services running in
+    // SYSTEM user only.
     @Override
+    @SuppressWarnings("NonUserGetterCalled")
     protected long getAutoDisconnectTimeoutMs() {
-        return Settings.Secure.getLongForUser(mContext.getContentResolver(),
-                Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
-                TimeUnit.SECONDS.toMillis(30),
-                mContext.getUserId());
+        return Settings.Secure.getLong(mContext.getContentResolver(),
+                ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
+                TimeUnit.SECONDS.toMillis(30));
     }
 }
diff --git a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
similarity index 98%
rename from services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
index 32f0698..249bcd3 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
@@ -21,7 +21,7 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
 
 import java.util.concurrent.TimeoutException;
 
@@ -32,6 +32,8 @@
  * some cases. Instead, in such cases we rely on the remote service sending progress updates and if
  * there are *no* progress callbacks in the duration of {@link #idleTimeoutMs}, we can assume the
  * download will not complete and enabling faster cleanup.
+ *
+ * @hide
  */
 public class ListenableDownloadCallback extends IDownloadCallback.Stub implements Runnable {
     private final IDownloadCallback callback;
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 50db501..5033101 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -45,6 +45,17 @@
       ]
     },
     {
+      "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases",
+      "options":[
+        {
+          "exclude-annotation":"androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation":"org.junit.Ignore"
+        }
+      ]
+    },
+    {
       "name": "CtsPackageInstallerCUJUpdateSelfTestCases",
       "options":[
         {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index d89d397..5a524d9 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -133,7 +133,7 @@
                 list.joinToString(separator = System.lineSeparator())
             }
         if (footer.isBlank()) return
-        HorizontalDivider()
+        if (!isSpaExpressiveEnabled) HorizontalDivider()
         Column(
             modifier =
                 if (isSpaExpressiveEnabled) Modifier.padding(SettingsDimension.footerItemPadding)
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 35e3dd3..e1be1d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -231,7 +231,7 @@
         public SignalStrength signalStrength;
         public TelephonyDisplayInfo telephonyDisplayInfo =
                 new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
+                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false);
 
         /**
          * Empty constructor
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 1f291cd..731cb72 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -99,6 +99,7 @@
         Settings.Secure.RTT_CALLING_MODE,
         Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
         Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED,
+        Settings.Secure.MIRROR_BUILT_IN_DISPLAY,
         Settings.Secure.MATCH_CONTENT_FRAME_RATE,
         Settings.Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
         Settings.Secure.NIGHT_DISPLAY_CUSTOM_END_TIME,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index abd5b9a..039832c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -150,6 +150,7 @@
                 Secure.INCALL_POWER_BUTTON_BEHAVIOR,
                 new DiscreteValueValidator(new String[] {"1", "2"}));
         VALIDATORS.put(Secure.MINIMAL_POST_PROCESSING_ALLOWED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.MIRROR_BUILT_IN_DISPLAY, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Secure.MATCH_CONTENT_FRAME_RATE,
                 new DiscreteValueValidator(new String[] {"0", "1", "2"}));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 9ab853f..e12c7a2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -22,6 +22,7 @@
 import android.app.backup.BackupAgentHelper;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.FullBackupDataOutput;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -82,6 +83,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
+import java.util.HashMap;
 import java.util.zip.CRC32;
 
 /**
@@ -194,6 +196,12 @@
     private static final String KEY_LOCK_SETTINGS_PIN_ENHANCED_PRIVACY =
             "pin_enhanced_privacy";
 
+    // Error messages for logging metrics.
+    private static final String ERROR_COULD_NOT_READ_FROM_CURSOR =
+        "could_not_read_from_cursor";
+    private static final String ERROR_FAILED_TO_WRITE_ENTITY =
+        "failed_to_write_entity";
+
     // Name of the temporary file we use during full backup/restore.  This is
     // stored in the full-backup tarfile as well, so should not be changed.
     private static final String STAGE_FILE = "flattened-data";
@@ -224,6 +232,10 @@
     // The font_scale default value for this device.
     private float mDefaultFontScale;
 
+    @Nullable private BackupRestoreEventLogger mBackupRestoreEventLogger;
+    @VisibleForTesting boolean areAgentMetricsEnabled = false;
+    @VisibleForTesting protected Map<String, Integer> numberOfSettingsPerKey;
+
     @Override
     public void onCreate() {
         if (DEBUG_BACKUP) Log.d(TAG, "onCreate() invoked");
@@ -232,6 +244,11 @@
                 .getStringArray(R.array.entryvalues_font_size);
         mSettingsHelper = new SettingsHelper(this);
         mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+        if (com.android.server.backup.Flags.enableMetricsSettingsBackupAgents()) {
+            mBackupRestoreEventLogger = this.getBackupRestoreEventLogger();
+            numberOfSettingsPerKey = new HashMap<>();
+            areAgentMetricsEnabled = true;
+        }
         super.onCreate();
     }
 
@@ -654,23 +671,41 @@
         if (oldChecksum == newChecksum) {
             return oldChecksum;
         }
+        writeDataForKey(key, data, output);
+        return newChecksum;
+    }
+
+    @VisibleForTesting
+    void writeDataForKey(String key, byte[] data, BackupDataOutput output) {
+        boolean shouldLogMetrics =
+            areAgentMetricsEnabled && numberOfSettingsPerKey.containsKey(key);
         try {
             if (DEBUG_BACKUP) {
                 Log.v(TAG, "Writing entity " + key + " of size " + data.length);
             }
             output.writeEntityHeader(key, data.length);
             output.writeEntityData(data, data.length);
+            if (shouldLogMetrics) {
+                mBackupRestoreEventLogger
+                    .logItemsBackedUp(key, numberOfSettingsPerKey.get(key));
+            }
         } catch (IOException ioe) {
             // Bail
+            if (shouldLogMetrics) {
+                mBackupRestoreEventLogger
+                    .logItemsBackupFailed(
+                        key,
+                        numberOfSettingsPerKey.get(key),
+                        ERROR_FAILED_TO_WRITE_ENTITY);
+            }
         }
-        return newChecksum;
     }
 
     private byte[] getSystemSettings() {
         Cursor cursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION, null,
                 null, null);
         try {
-            return extractRelevantValues(cursor, SystemSettings.SETTINGS_TO_BACKUP);
+            return extractRelevantValues(cursor, SystemSettings.SETTINGS_TO_BACKUP, KEY_SYSTEM);
         } finally {
             cursor.close();
         }
@@ -680,7 +715,7 @@
         Cursor cursor = getContentResolver().query(Settings.Secure.CONTENT_URI, PROJECTION, null,
                 null, null);
         try {
-            return extractRelevantValues(cursor, SecureSettings.SETTINGS_TO_BACKUP);
+            return extractRelevantValues(cursor, SecureSettings.SETTINGS_TO_BACKUP, KEY_SECURE);
         } finally {
             cursor.close();
         }
@@ -690,7 +725,7 @@
         Cursor cursor = getContentResolver().query(Settings.Global.CONTENT_URI, PROJECTION, null,
                 null, null);
         try {
-            return extractRelevantValues(cursor, getGlobalSettingsToBackup());
+            return extractRelevantValues(cursor, getGlobalSettingsToBackup(), KEY_GLOBAL);
         } finally {
             cursor.close();
         }
@@ -1118,11 +1153,20 @@
      *
      * @param cursor A cursor with settings data.
      * @param settings The settings to extract.
+     * @param settingsKey The key of the settings to extract (eg system).
      * @return The byte array of extracted values.
      */
-    private byte[] extractRelevantValues(Cursor cursor, String[] settings) {
+    private byte[] extractRelevantValues(
+        Cursor cursor, String[] settings, String settingsKey) {
         if (!cursor.moveToFirst()) {
             Log.e(TAG, "Couldn't read from the cursor");
+            if (areAgentMetricsEnabled) {
+                mBackupRestoreEventLogger
+                    .logItemsBackupFailed(
+                        settingsKey,
+                        settings.length,
+                        ERROR_COULD_NOT_READ_FROM_CURSOR);
+            }
             return new byte[0];
         }
 
@@ -1181,6 +1225,10 @@
             }
         }
 
+        if (areAgentMetricsEnabled) {
+            numberOfSettingsPerKey.put(settingsKey, backedUpSettingIndex);
+        }
+
         // Aggregate the result.
         byte[] result = new byte[totalSize];
         int pos = 0;
@@ -1364,7 +1412,9 @@
                      getContentResolver()
                              .query(Settings.Secure.CONTENT_URI, PROJECTION, null, null, null)) {
             return extractRelevantValues(
-                    cursor, DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+                    cursor,
+                    DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP,
+                    KEY_DEVICE_SPECIFIC_CONFIG);
         }
     }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index c0e61ee..7aed615 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -409,6 +409,11 @@
                         Slog.w(LOG_TAG, "Bulk sync request to acongid failed.");
                     }
                 }
+
+                if (Flags.disableBulkCompare()) {
+                    return;
+                }
+
                 // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
                 if (requests == null) {
                     Map<String, AconfigdFlagInfo> aconfigdFlagMap =
@@ -421,7 +426,7 @@
         }
     }
 
-    // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
+    // TODO(b/312444587): remove the comparison logic after Test Mission 2.
     public int compareFlagValueInNewStorage(
             Map<String, AconfigdFlagInfo> defaultFlagMap,
             Map<String, AconfigdFlagInfo> aconfigdFlagMap) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index aca26ec..cfd27c6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -101,3 +101,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "disable_bulk_compare"
+    namespace: "core_experiments_team_internal"
+    description: "Disable bulk comparison between DeviceConfig and aconfig storage."
+    bug: "312444587"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index 3a391505..4642864 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -17,11 +17,22 @@
 package com.android.providers.settings;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
 
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupRestoreEventLogger;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -32,7 +43,10 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.provider.settings.validators.SettingsValidators;
 import android.provider.settings.validators.Validator;
@@ -44,8 +58,12 @@
 import com.android.window.flags.Flags;
 
 import org.junit.Before;
+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;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -54,12 +72,14 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 
+
 /**
  * Tests for the SettingsHelperTest
  * Usage: atest SettingsProviderTest:SettingsBackupAgentTest
@@ -73,6 +93,8 @@
     private static final Map<String, String> DEVICE_SPECIFIC_TEST_VALUES = new HashMap<>();
     private static final Map<String, String> TEST_VALUES = new HashMap<>();
     private static final Map<String, Validator> TEST_VALUES_VALIDATORS = new HashMap<>();
+    private static final String TEST_KEY = "test_key";
+    private static final String TEST_VALUE = "test_value";
 
     static {
         DEVICE_SPECIFIC_TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED,
@@ -86,6 +108,13 @@
         TEST_VALUES_VALIDATORS.put(PRESERVED_TEST_SETTING, SettingsValidators.ANY_STRING_VALIDATOR);
     }
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock private BackupDataOutput mBackupDataOutput;
+
     private TestFriendlySettingsBackupAgent mAgentUnderTest;
     private Context mContext;
 
@@ -262,6 +291,110 @@
         assertEquals("1.5", testedMethod.apply("1.8"));
     }
 
+    @Test
+    @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void onCreate_metricsFlagIsDisabled_areAgentMetricsEnabledIsFalse() {
+        mAgentUnderTest.onCreate();
+
+        assertFalse(mAgentUnderTest.areAgentMetricsEnabled);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void onCreate_flagIsEnabled_areAgentMetricsEnabledIsTrue() {
+        mAgentUnderTest.onCreate();
+
+        assertTrue(mAgentUnderTest.areAgentMetricsEnabled);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_dataWriteSucceeds_logsSuccessMetrics()
+        throws IOException {
+        when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
+        when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
+        mAgentUnderTest.onCreate(
+            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+        mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);
+
+        mAgentUnderTest.writeDataForKey(
+            TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);
+
+        DataTypeResult loggingResult =
+            getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+        assertNotNull(loggingResult);
+        assertEquals(loggingResult.getSuccessCount(), 1);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_writeEntityHeaderFails_logsFailureMetrics()
+        throws IOException {
+        when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenThrow(new IOException());
+        when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
+        mAgentUnderTest.onCreate(
+            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+        mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);
+
+        mAgentUnderTest.writeDataForKey(
+            TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);
+
+        DataTypeResult loggingResult =
+            getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+        assertNotNull(loggingResult);
+        assertEquals(loggingResult.getFailCount(), 1);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_writeEntityDataFails_logsFailureMetrics()
+        throws IOException {
+        when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
+        when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenThrow(new IOException());
+        mAgentUnderTest.onCreate(
+            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+        mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);
+
+        mAgentUnderTest.writeDataForKey(
+            TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);
+
+        DataTypeResult loggingResult =
+            getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+        assertNotNull(loggingResult);
+        assertEquals(loggingResult.getFailCount(), 1);
+    }
+
+    @Test
+    @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void writeDataForKey_metricsFlagIsDisabled_doesNotLogMetrics()
+        throws IOException {
+        when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
+        when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
+        mAgentUnderTest.onCreate(
+            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+        mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);
+
+        mAgentUnderTest.writeDataForKey(
+            TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);
+
+        assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
+    }
+
+    @Test
+    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyDoesNotContainKey_doesNotLogMetrics()
+        throws IOException {
+        when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
+        when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
+        mAgentUnderTest.onCreate(
+            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+
+        mAgentUnderTest.writeDataForKey(
+            TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);
+
+        assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
+    }
+
     private byte[] generateBackupData(Map<String, String> keyValueData) {
         int totalBytes = 0;
         for (String key : keyValueData.keySet()) {
@@ -329,6 +462,21 @@
         }
     }
 
+    private DataTypeResult getLoggingResultForDatatype(
+        String dataType, SettingsBackupAgent agent) {
+        if (agent.getBackupRestoreEventLogger() == null) {
+            return null;
+        }
+        List<DataTypeResult> loggingResults =
+            agent.getBackupRestoreEventLogger().getLoggingResults();
+        for (DataTypeResult result : loggingResults) {
+            if (result.getDataType().equals(dataType)) {
+                return result;
+            }
+        }
+        return null;
+    }
+
     private byte[] generateSingleKeyTestBackupData(String key, String value) throws IOException {
         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
             os.write(SettingsBackupAgent.toByteArray(key));
@@ -376,6 +524,12 @@
 
             return mSettingsWhitelist;
         }
+
+        void setNumberOfSettingsPerKey(String key, int numberOfSettings) {
+            if (numberOfSettingsPerKey != null) {
+                this.numberOfSettingsPerKey.put(key, numberOfSettings);
+            }
+        }
     }
 
     /** The TestSettingsHelper tracks which values have been backed up and/or restored. */
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 48ce49d..276b206 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -27,6 +27,7 @@
 import android.aconfigd.AconfigdFlagInfo;
 import android.os.Looper;
 import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.Xml;
@@ -1304,6 +1305,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DISABLE_BULK_COMPARE)
     public void testCompareFlagValueInNewStorage() {
                 int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
         Object lock = new Object();
diff --git a/packages/Shell/Android.bp b/packages/Shell/Android.bp
index 5f81085..5fdf045 100644
--- a/packages/Shell/Android.bp
+++ b/packages/Shell/Android.bp
@@ -12,7 +12,10 @@
     "src/**/*.java",
     ":dumpstate_aidl",
 ]
-shell_static_libs = ["androidx.legacy_legacy-support-v4"]
+shell_static_libs = [
+    "androidx.legacy_legacy-support-v4",
+    "wear_aconfig_declarations_flags_java_lib",
+]
 
 android_app {
     name: "Shell",
@@ -28,6 +31,7 @@
     flags_packages: [
         "android.security.flags-aconfig",
         "android.permission.flags-aconfig",
+        "wear_aconfig_declarations",
     ],
     platform_apis: true,
     certificate: "platform",
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index fa6e2db..baf829a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -740,6 +740,9 @@
     <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
     <uses-permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE" />
 
+    <!-- Permission required to access bugreport and screenshot files created by wear.  -->
+    <uses-permission android:name="com.google.wear.permission.ACCESS_BUG_REPORT_FILES" />
+
     <!-- Permission required to run GtsAssistantTestCases -->
     <uses-permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES" />
 
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 7f25b51..9736831 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -24,6 +24,7 @@
 import static com.android.shell.BugreportPrefs.STATE_HIDE;
 import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
 import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.flags.Flags.handleBugreportsForWear;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -89,10 +90,10 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
-import com.google.android.collect.Lists;
-
 import libcore.io.Streams;
 
+import com.google.android.collect.Lists;
+
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -140,6 +141,7 @@
 public class BugreportProgressService extends Service {
     private static final String TAG = "BugreportProgressService";
     private static final boolean DEBUG = false;
+    private static final String WRITE_AND_APPEND_MODE = "wa";
 
     private Intent startSelfIntent;
 
@@ -384,7 +386,11 @@
     }
 
     private static String getFileName(BugreportInfo info, String suffix) {
-        return String.format("%s-%s%s", info.baseName, info.getName(), suffix);
+        return getFileName(suffix, info.baseName, info.getName());
+    }
+
+    private static String getFileName(String suffix, String baseName, String name) {
+        return String.format("%s-%s%s", baseName, name, suffix);
     }
 
     private final class BugreportCallbackImpl extends BugreportCallback {
@@ -420,14 +426,14 @@
 
         @Override
         public void onFinished() {
-            mInfo.renameBugreportFile();
-            mInfo.renameScreenshots();
-            if (mInfo.bugreportFile.length() == 0) {
-                Log.e(TAG, "Bugreport file empty. File path = " + mInfo.bugreportFile);
-                onError(BUGREPORT_ERROR_RUNTIME);
-                return;
-            }
             synchronized (mLock) {
+                mInfo.renameBugreportFile();
+                mInfo.renameScreenshots();
+                if (mInfo.bugreportLocationInfo.isFileEmpty(mContext)) {
+                    Log.e(TAG, "Bugreport file empty. File path = " + mInfo.bugreportLocationInfo);
+                    onError(BUGREPORT_ERROR_RUNTIME);
+                    return;
+                }
                 sendBugreportFinishedBroadcastLocked();
                 mMainThreadHandler.post(() -> mInfoDialog.onBugreportFinished(mInfo));
             }
@@ -454,15 +460,15 @@
 
         @GuardedBy("mLock")
         private void sendBugreportFinishedBroadcastLocked() {
-            final String bugreportFilePath = mInfo.bugreportFile.getAbsolutePath();
-            if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE) {
-                sendRemoteBugreportFinishedBroadcast(mContext, bugreportFilePath,
-                        mInfo.bugreportFile, mInfo.nonce);
+            File bugreportFile = mInfo.bugreportLocationInfo.mBugreportFile;
+            if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE && bugreportFile != null) {
+                sendRemoteBugreportFinishedBroadcast(
+                        mContext, bugreportFile.getAbsolutePath(), bugreportFile, mInfo.nonce);
             } else {
                 cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir);
                 final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
-                intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
-                intent.putExtra(EXTRA_SCREENSHOT, getScreenshotForIntent(mInfo));
+                intent.putExtra(EXTRA_BUGREPORT, mInfo.bugreportLocationInfo.getBugreportPath());
+                intent.putExtra(EXTRA_SCREENSHOT, mInfo.screenshotLocationInfo.getScreenshotPath());
                 mContext.sendBroadcast(intent, android.Manifest.permission.DUMP);
                 onBugreportFinished(mInfo);
             }
@@ -498,19 +504,6 @@
                 android.Manifest.permission.DUMP);
     }
 
-    /**
-     * Checks if screenshot array is non-empty and returns the first screenshot's path. The first
-     * screenshot is the default screenshot for the bugreport types that take it.
-     */
-    private static String getScreenshotForIntent(BugreportInfo info) {
-        if (!info.screenshotFiles.isEmpty()) {
-            final File screenshotFile = info.screenshotFiles.get(0);
-            final String screenshotFilePath = screenshotFile.getAbsolutePath();
-            return screenshotFilePath;
-        }
-        return null;
-    }
-
     private static String generateFileHash(String fileName) {
         String fileHash = null;
         try {
@@ -715,24 +708,28 @@
         String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
         List<Uri> extraAttachments =
                 intent.getParcelableArrayListExtra(EXTRA_EXTRA_ATTACHMENT_URIS, Uri.class);
-
-        BugreportInfo info = new BugreportInfo(mContext, baseName, name, shareTitle,
-                shareDescription, bugreportType, mBugreportsDir, nonce, extraAttachments);
-        synchronized (mLock) {
-            if (info.bugreportFile.exists()) {
-                Log.e(TAG, "Failed to start bugreport generation, the requested bugreport file "
-                        + info.bugreportFile + " already exists");
-                return;
-            }
-            info.createBugreportFile();
+        BugreportInfo info =
+                setupFilesAndCreateBugreportInfo(
+                        intent,
+                        bugreportType,
+                        baseName,
+                        name,
+                        shareTitle,
+                        shareDescription,
+                        nonce,
+                        extraAttachments);
+        if (info == null) {
+            Log.e(TAG, "Could not initialize bugreport inputs");
+            return;
         }
+
         ParcelFileDescriptor bugreportFd = info.getBugreportFd();
         if (bugreportFd == null) {
             Log.e(TAG, "Failed to start bugreport generation as "
                     + " bugreport parcel file descriptor is null.");
             return;
         }
-        info.createScreenshotFile(mBugreportsDir);
+
         ParcelFileDescriptor screenshotFd = null;
         if (isDefaultScreenshotRequired(bugreportType, /* hasScreenshotButton= */ !mIsTv)) {
             screenshotFd = info.getDefaultScreenshotFd();
@@ -740,7 +737,7 @@
                 Log.e(TAG, "Failed to start bugreport generation as"
                         + " screenshot parcel file descriptor is null. Deleting bugreport file");
                 FileUtils.closeQuietly(bugreportFd);
-                info.bugreportFile.delete();
+                info.bugreportLocationInfo.maybeDeleteBugreportFile();
                 return;
             }
         }
@@ -768,6 +765,56 @@
         }
     }
 
+    // Sets up BugreportInfo. If needed, creates bugreport and screenshot files.
+    private BugreportInfo setupFilesAndCreateBugreportInfo(
+            Intent intent,
+            int bugreportType,
+            String baseName,
+            String name,
+            String shareTitle,
+            String shareDescription,
+            long nonce,
+            List<Uri> extraAttachments) {
+        ArrayList<Uri> brAndScreenshot;
+        Uri bugReportUri = null;
+        Uri screenshotUri = null;
+
+        if (handleBugreportsForWear() && bugreportType == BugreportParams.BUGREPORT_MODE_WEAR) {
+            brAndScreenshot = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+            if (brAndScreenshot != null && !brAndScreenshot.isEmpty()) {
+                bugReportUri = brAndScreenshot.get(0);
+                if (bugReportUri == null) {
+                    Log.e(TAG, "Can't start bugreport request. Bugreport uri is null.");
+                    return null;
+                }
+                screenshotUri = (brAndScreenshot.size() > 1) ? brAndScreenshot.get(1) : null;
+            }
+        }
+
+        BugreportLocationInfo bugreportLocationInfo =
+                new BugreportLocationInfo(bugReportUri, mBugreportsDir, baseName, name);
+        ScreenshotLocationInfo screenshotLocationInfo = new ScreenshotLocationInfo(screenshotUri);
+        BugreportInfo info =
+                new BugreportInfo(
+                        mContext,
+                        baseName,
+                        name,
+                        shareTitle,
+                        shareDescription,
+                        bugreportType,
+                        nonce,
+                        extraAttachments,
+                        bugreportLocationInfo,
+                        screenshotLocationInfo);
+        synchronized (mLock) {
+            if (!bugreportLocationInfo.maybeCreateBugreportFile()) {
+                return null;
+            }
+        }
+        info.maybeCreateScreenshotFile(mBugreportsDir);
+        return info;
+    }
+
     private static boolean isDefaultScreenshotRequired(
             @BugreportParams.BugreportMode int bugreportType,
             boolean hasScreenshotButton) {
@@ -1177,8 +1224,9 @@
             stopForegroundWhenDoneLocked(info.id);
         }
 
-        if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) {
-            Log.e(TAG, "Could not read bugreport file " + info.bugreportFile);
+        File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+        if (!info.bugreportLocationInfo.isValidBugreportResult()) {
+            Log.e(TAG, "Could not read bugreport file " + bugreportFile);
             Toast.makeText(mContext, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show();
             synchronized (mLock) {
                 stopProgressLocked(info.id);
@@ -1194,7 +1242,7 @@
      * the bugreport.
      */
     private void triggerLocalNotification(final BugreportInfo info) {
-        boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt");
+        boolean isPlainText = info.bugreportLocationInfo.isPlainText();
         if (!isPlainText) {
             // Already zipped, send it right away.
             sendBugreportNotification(info, mTakingScreenshot);
@@ -1223,11 +1271,11 @@
         // grant temporary permissions for.
         final Uri bugreportUri;
         try {
-            bugreportUri = getUri(context, info.bugreportFile);
+            bugreportUri = getUri(context, info.bugreportLocationInfo.mBugreportFile);
         } catch (IllegalArgumentException e) {
             // Should not happen on production, but happens when a Shell is sideloaded and
             // FileProvider cannot find a configured root for it.
-            Log.wtf(TAG, "Could not get URI for " + info.bugreportFile, e);
+            Log.wtf(TAG, "Could not get URI for " + info.bugreportLocationInfo.mBugreportFile, e);
             return null;
         }
 
@@ -1258,7 +1306,7 @@
                 new ClipData.Item(null, null, null, bugreportUri));
         Log.d(TAG, "share intent: bureportUri=" + bugreportUri);
         final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
-        for (File screenshot : info.screenshotFiles) {
+        for (File screenshot : info.screenshotLocationInfo.mScreenshotFiles) {
             final Uri screenshotUri = getUri(context, screenshot);
             Log.d(TAG, "share intent: screenshotUri=" + screenshotUri);
             clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
@@ -1512,22 +1560,25 @@
      * original in case of failure).
      */
     private static void zipBugreport(BugreportInfo info) {
-        final String bugreportPath = info.bugreportFile.getAbsolutePath();
+        File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+        final String bugreportPath = bugreportFile.getAbsolutePath();
         final String zippedPath = bugreportPath.replace(".txt", ".zip");
         Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
         final File bugreportZippedFile = new File(zippedPath);
-        try (InputStream is = new FileInputStream(info.bugreportFile);
-                ZipOutputStream zos = new ZipOutputStream(
-                        new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
-            addEntry(zos, info.bugreportFile.getName(), is);
+        try (InputStream is = new FileInputStream(bugreportFile);
+                ZipOutputStream zos =
+                        new ZipOutputStream(
+                                new BufferedOutputStream(
+                                        new FileOutputStream(bugreportZippedFile)))) {
+            addEntry(zos, bugreportFile.getName(), is);
             // Delete old file
-            final boolean deleted = info.bugreportFile.delete();
+            final boolean deleted = bugreportFile.delete();
             if (deleted) {
                 Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
             } else {
                 Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
             }
-            info.bugreportFile = bugreportZippedFile;
+            info.bugreportLocationInfo.mBugreportFile = bugreportZippedFile;
         } catch (IOException e) {
             Log.e(TAG, "exception zipping file " + zippedPath, e);
         }
@@ -1557,7 +1608,11 @@
 
     @GuardedBy("mLock")
     private void addDetailsToZipFileLocked(BugreportInfo info) {
-        if (info.bugreportFile == null) {
+        if (handleBugreportsForWear()) {
+            Log.d(TAG, "Skipping adding details to zipped file");
+            return;
+        }
+        if (info.bugreportLocationInfo.mBugreportFile == null) {
             // One possible reason is a bug in the Parcelization code.
             Log.wtf(TAG, "addDetailsToZipFile(): no bugreportFile on " + info);
             return;
@@ -1588,10 +1643,11 @@
             sendBugreportBeingUpdatedNotification(mContext, info.id); // ...and that takes time
         }
 
-        final File dir = info.bugreportFile.getParentFile();
-        final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
+        File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+        final File dir = bugreportFile.getParentFile();
+        final File tmpZip = new File(dir, "tmp-" + bugreportFile.getName());
         Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description");
-        try (ZipFile oldZip = new ZipFile(info.bugreportFile);
+        try (ZipFile oldZip = new ZipFile(bugreportFile);
                 ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) {
 
             // First copy contents from original zip.
@@ -1628,8 +1684,8 @@
             stopForegroundWhenDoneLocked(info.id);
         }
 
-        if (!tmpZip.renameTo(info.bugreportFile)) {
-            Log.e(TAG, "Could not rename " + tmpZip + " to " + info.bugreportFile);
+        if (!tmpZip.renameTo(bugreportFile)) {
+            Log.e(TAG, "Could not rename " + tmpZip + " to " + bugreportFile);
         }
     }
 
@@ -2087,15 +2143,9 @@
          */
         String formattedLastUpdate;
 
-        /**
-         * Path of the main bugreport file.
-         */
-        File bugreportFile;
+        BugreportLocationInfo bugreportLocationInfo;
 
-        /**
-         * Path of the screenshot files.
-         */
-        List<File> screenshotFiles = new ArrayList<>(1);
+        ScreenshotLocationInfo screenshotLocationInfo;
 
         /**
          * Whether dumpstate sent an intent informing it has finished.
@@ -2138,10 +2188,17 @@
         /**
          * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
          */
-        BugreportInfo(Context context, String baseName, String name,
-                @Nullable String shareTitle, @Nullable String shareDescription,
-                @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce,
-                @Nullable List<Uri> extraAttachments) {
+        BugreportInfo(
+                Context context,
+                String baseName,
+                String name,
+                @Nullable String shareTitle,
+                @Nullable String shareDescription,
+                @BugreportParams.BugreportMode int type,
+                long nonce,
+                @Nullable List<Uri> extraAttachments,
+                BugreportLocationInfo bugreportLocationInfo,
+                ScreenshotLocationInfo screenshotLocationInfo) {
             this.context = context;
             this.name = this.initialName = name;
             this.shareTitle = shareTitle == null ? "" : shareTitle;
@@ -2149,29 +2206,27 @@
             this.type = type;
             this.nonce = nonce;
             this.baseName = baseName;
-            this.bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+            this.bugreportLocationInfo = bugreportLocationInfo;
+            this.screenshotLocationInfo = screenshotLocationInfo;
             this.extraAttachments = extraAttachments;
         }
 
-        void createBugreportFile() {
-            createReadWriteFile(bugreportFile);
-        }
-
-        void createScreenshotFile(File bugreportsDir) {
+        void maybeCreateScreenshotFile(File bugreportsDir) {
+            if (screenshotLocationInfo.mScreenshotUri != null) {
+                // Screenshot file was already created.
+                return;
+            }
             File screenshotFile = new File(bugreportsDir, getScreenshotName("default"));
             addScreenshot(screenshotFile);
             createReadWriteFile(screenshotFile);
         }
 
         ParcelFileDescriptor getBugreportFd() {
-            return getFd(bugreportFile);
+            return bugreportLocationInfo.getBugreportFd(context);
         }
 
         ParcelFileDescriptor getDefaultScreenshotFd() {
-            if (screenshotFiles.isEmpty()) {
-                return null;
-            }
-            return getFd(screenshotFiles.get(0));
+            return screenshotLocationInfo.getScreenshotFd(context);
         }
 
         void setTitle(String title) {
@@ -2229,14 +2284,14 @@
          * Saves the location of a taken screenshot so it can be sent out at the end.
          */
         void addScreenshot(File screenshot) {
-            screenshotFiles.add(screenshot);
+            screenshotLocationInfo.mScreenshotFiles.add(screenshot);
         }
 
         /**
          * Deletes all screenshots taken for a given bugreport.
          */
         private void deleteScreenshots() {
-            for (File file : screenshotFiles) {
+            for (File file : screenshotLocationInfo.mScreenshotFiles) {
                 Log.i(TAG, "Deleting screenshot file " + file);
                 file.delete();
             }
@@ -2246,18 +2301,14 @@
          * Deletes bugreport file for a given bugreport.
          */
         private void deleteBugreportFile() {
-            Log.i(TAG, "Deleting bugreport file " + bugreportFile);
-            bugreportFile.delete();
+            bugreportLocationInfo.maybeDeleteBugreportFile();
         }
 
         /**
          * Deletes empty files for a given bugreport.
          */
         private void deleteEmptyFiles() {
-            if (bugreportFile.length() == 0) {
-                Log.i(TAG, "Deleting empty bugreport file: " + bugreportFile);
-                bugreportFile.delete();
-            }
+            bugreportLocationInfo.maybeDeleteEmptyBugreport();
             deleteEmptyScreenshots();
         }
 
@@ -2265,14 +2316,7 @@
          * Deletes empty screenshot files.
          */
         private void deleteEmptyScreenshots() {
-            screenshotFiles.removeIf(file -> {
-                final long length = file.length();
-                if (length == 0) {
-                    Log.i(TAG, "Deleting empty screenshot file: " + file);
-                    file.delete();
-                }
-                return length == 0;
-            });
+            screenshotLocationInfo.deleteEmptyScreenshots();
         }
 
         /**
@@ -2280,43 +2324,14 @@
          * {@code initialName} if user has changed it.
          */
         void renameScreenshots() {
-            deleteEmptyScreenshots();
-            if (TextUtils.isEmpty(name) || screenshotFiles.isEmpty()) {
-                return;
-            }
-            final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
-            for (File oldFile : screenshotFiles) {
-                final String oldName = oldFile.getName();
-                final String newName = oldName.replaceFirst(initialName, name);
-                final File newFile;
-                if (!newName.equals(oldName)) {
-                    final File renamedFile = new File(oldFile.getParentFile(), newName);
-                    Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
-                    newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
-                } else {
-                    Log.w(TAG, "Name didn't change: " + oldName);
-                    newFile = oldFile;
-                }
-                if (newFile.length() > 0) {
-                    renamedFiles.add(newFile);
-                } else if (newFile.delete()) {
-                    Log.d(TAG, "screenshot file: " + newFile + " deleted successfully.");
-                }
-            }
-            screenshotFiles = renamedFiles;
+            screenshotLocationInfo.renameScreenshots(initialName, name);
         }
 
         /**
          * Rename bugreport file to include the name given by user via UI
          */
         void renameBugreportFile() {
-            File newBugreportFile = new File(bugreportFile.getParentFile(),
-                    getFileName(this, ".zip"));
-            if (!newBugreportFile.getPath().equals(bugreportFile.getPath())) {
-                if (bugreportFile.renameTo(newBugreportFile)) {
-                    bugreportFile = newBugreportFile;
-                }
-            }
+            bugreportLocationInfo.maybeRenameBugreportFile(this);
         }
 
         String getFormattedLastUpdate() {
@@ -2349,16 +2364,23 @@
                 builder.append("(").append(description.length()).append(" chars)");
             }
 
-            return builder
-                .append("\n\tfile: ").append(bugreportFile)
-                .append("\n\tscreenshots: ").append(screenshotFiles)
-                .append("\n\tprogress: ").append(progress)
-                .append("\n\tlast_update: ").append(getFormattedLastUpdate())
-                .append("\n\taddingDetailsToZip: ").append(addingDetailsToZip)
-                .append(" addedDetailsToZip: ").append(addedDetailsToZip)
-                .append("\n\tshareDescription: ").append(shareDescription)
-                .append("\n\tshareTitle: ").append(shareTitle)
-                .toString();
+            return builder.append("\n\tfile: ")
+                    .append(bugreportLocationInfo)
+                    .append("\n\tscreenshots: ")
+                    .append(screenshotLocationInfo)
+                    .append("\n\tprogress: ")
+                    .append(progress)
+                    .append("\n\tlast_update: ")
+                    .append(getFormattedLastUpdate())
+                    .append("\n\taddingDetailsToZip: ")
+                    .append(addingDetailsToZip)
+                    .append(" addedDetailsToZip: ")
+                    .append(addedDetailsToZip)
+                    .append("\n\tshareDescription: ")
+                    .append(shareDescription)
+                    .append("\n\tshareTitle: ")
+                    .append(shareTitle)
+                    .toString();
         }
 
         // Parcelable contract
@@ -2375,11 +2397,12 @@
             lastProgress.set(in.readInt());
             lastUpdate.set(in.readLong());
             formattedLastUpdate = in.readString();
-            bugreportFile = readFile(in);
+            bugreportLocationInfo = new BugreportLocationInfo(readFile(in));
 
             int screenshotSize = in.readInt();
             for (int i = 1; i <= screenshotSize; i++) {
-                  screenshotFiles.add(readFile(in));
+                screenshotLocationInfo = new ScreenshotLocationInfo(null);
+                screenshotLocationInfo.mScreenshotFiles.add(readFile(in));
             }
 
             finished.set(in.readInt() == 1);
@@ -2404,10 +2427,10 @@
             dest.writeInt(lastProgress.intValue());
             dest.writeLong(lastUpdate.longValue());
             dest.writeString(getFormattedLastUpdate());
-            writeFile(dest, bugreportFile);
+            writeFile(dest, bugreportLocationInfo.mBugreportFile);
 
-            dest.writeInt(screenshotFiles.size());
-            for (File screenshotFile : screenshotFiles) {
+            dest.writeInt(screenshotLocationInfo.mScreenshotFiles.size());
+            for (File screenshotFile : screenshotLocationInfo.mScreenshotFiles) {
                 writeFile(dest, screenshotFile);
             }
 
@@ -2449,6 +2472,261 @@
         };
     }
 
+    /**
+     * Class for abstracting bugreport location. There are two possible cases:
+     * <li>If a bugreport request included a URI for bugreports of type {@link
+     *     BugreportParams.BUGREPORT_MODE_WEAR}, then the URI file descriptor will be used. The
+     *     requesting app manages the creation and lifecycle of the file.
+     * <li>If no URI is provided in the bugreport request, Shell will create a bugreport file and
+     *     manage its lifecycle.
+     */
+    private static final class BugreportLocationInfo {
+        /** Path of the main bugreport file. */
+        @Nullable private File mBugreportFile;
+
+        /** Uri to bugreport location. */
+        @Nullable private Uri mBugreportUri;
+
+        BugreportLocationInfo(File bugreportFile) {
+            this.mBugreportFile = bugreportFile;
+        }
+
+        BugreportLocationInfo(Uri bugreportUri, File bugreportsDir, String baseName, String name) {
+            if (bugreportUri != null) {
+                this.mBugreportUri = bugreportUri;
+            } else {
+                this.mBugreportFile = new File(bugreportsDir, getFileName(".zip", baseName, name));
+            }
+        }
+
+        private boolean maybeCreateBugreportFile() {
+            if (mBugreportFile != null && mBugreportFile.exists()) {
+                Log.e(
+                        TAG,
+                        "Failed to start bugreport generation, the requested bugreport file "
+                                + mBugreportFile
+                                + " already exists");
+                return false;
+            }
+            createBugreportFile();
+            return true;
+        }
+
+        private void createBugreportFile() {
+            if (mBugreportUri == null) {
+                createReadWriteFile(mBugreportFile);
+            }
+        }
+
+        private ParcelFileDescriptor getBugreportFd(Context context) {
+            if (mBugreportUri != null) {
+                try {
+                    return context.getContentResolver()
+                            .openFileDescriptor(mBugreportUri, WRITE_AND_APPEND_MODE);
+                } catch (Exception e) {
+                    Log.d(TAG, "Faced exception when getting BR file descriptor", e);
+                    return null;
+                }
+            }
+            if (mBugreportFile == null) {
+                Log.e(TAG, "Could not get bugreport file descriptor; bugreport file was null");
+                return null;
+            }
+            return getFd(mBugreportFile);
+        }
+
+        private void maybeDeleteBugreportFile() {
+            if (mBugreportFile == null) {
+                // This means a URI is provided and shell is not responsible for the file's
+                // lifecycle.
+                return;
+            }
+            Log.i(TAG, "Deleting bugreport file " + mBugreportFile);
+            mBugreportFile.delete();
+        }
+
+        private boolean isValidBugreportResult() {
+            if (mBugreportFile != null) {
+                return mBugreportFile.exists() && mBugreportFile.canRead();
+            }
+            // If a bugreport uri was provided, we can't assert on whether the file exists and can
+            // be read. Assume the result is valid.
+            return true;
+        }
+
+        private void maybeDeleteEmptyBugreport() {
+            if (mBugreportFile == null) {
+                // This means a URI is provided and shell is not responsible for the file's
+                // lifecycle.
+                return;
+            }
+            if (mBugreportFile.length() == 0) {
+                Log.i(TAG, "Deleting empty bugreport file: " + mBugreportFile);
+                mBugreportFile.delete();
+            }
+        }
+
+        private void maybeRenameBugreportFile(BugreportInfo bugreportInfo) {
+            if (mBugreportFile == null) {
+                // This means a URI is provided and shell is not responsible for the file's naming.
+                return;
+            }
+            File newBugreportFile =
+                    new File(mBugreportFile.getParentFile(), getFileName(bugreportInfo, ".zip"));
+            if (!newBugreportFile.getPath().equals(mBugreportFile.getPath())) {
+                if (mBugreportFile.renameTo(newBugreportFile)) {
+                    mBugreportFile = newBugreportFile;
+                }
+            }
+        }
+
+        private boolean isPlainText() {
+            if (mBugreportFile != null) {
+                return mBugreportFile.getName().toLowerCase().endsWith(".txt");
+            }
+            return false;
+        }
+
+        private boolean isFileEmpty(Context context) {
+            if (mBugreportFile != null) {
+                return mBugreportFile.length() == 0;
+            }
+            return getBugreportFd(context).getStatSize() == 0;
+        }
+
+        @Override
+        public String toString() {
+            return "BugreportLocationInfo{"
+                    + "bugreportFile="
+                    + mBugreportFile
+                    + ", bugreportUri="
+                    + mBugreportUri
+                    + '}';
+        }
+
+        private String getBugreportPath() {
+            if (mBugreportUri != null) {
+                return mBugreportUri.getLastPathSegment();
+            }
+            return mBugreportFile.getAbsolutePath();
+        }
+    }
+
+    /**
+     * Class for abstracting screenshot location. There are two possible cases:
+     * <li>If a bugreport request included a URI for bugreports of type {@link
+     *     BugreportParams.BUGREPORT_MODE_WEAR}, then the URI file descriptor will be used. The
+     *     requesting app manages the creation and lifecycle of the file.
+     * <li>If no URI is provided in the bugreport request, Shell will create the screenshot file and
+     *     manage its lifecycle.
+     */
+    private static final class ScreenshotLocationInfo {
+
+        /** Uri to screenshot location. */
+        @Nullable private Uri mScreenshotUri;
+
+        /** Path to screenshot files. */
+        private List<File> mScreenshotFiles = new ArrayList<>(1);
+
+        ScreenshotLocationInfo(Uri screenshotUri) {
+            if (screenshotUri != null) {
+                this.mScreenshotUri = screenshotUri;
+            }
+        }
+
+        private ParcelFileDescriptor getScreenshotFd(Context context) {
+            if (mScreenshotUri != null) {
+                try {
+                    return context.getContentResolver()
+                            .openFileDescriptor(mScreenshotUri, WRITE_AND_APPEND_MODE);
+                } catch (Exception e) {
+                    Log.d(TAG, "Faced exception when getting screenshot file", e);
+                    return null;
+                }
+            }
+
+            if (mScreenshotFiles.isEmpty()) {
+                return null;
+            }
+            return getFd(mScreenshotFiles.getFirst());
+        }
+
+        @Override
+        public String toString() {
+            return "ScreenshotLocationInfo{"
+                    + "screenshotUri="
+                    + mScreenshotUri
+                    + ", screenshotFiles="
+                    + mScreenshotFiles
+                    + '}';
+        }
+
+        private String getScreenshotPath() {
+            if (mScreenshotUri != null) {
+                return mScreenshotUri.getLastPathSegment();
+            }
+            return getScreenshotForIntent();
+        }
+
+        private void renameScreenshots(String initialName, String name) {
+            if (mScreenshotUri != null) {
+                // If a screenshot uri is provided, then shell is not responsible for the
+                // screenshot's naming.
+                return;
+            }
+            deleteEmptyScreenshots();
+            if (TextUtils.isEmpty(name) || mScreenshotFiles.isEmpty()) {
+                // If there is no user set name for screenshot file or there are no screenshot
+                // files, there's nothing to do.
+                return;
+            }
+            final List<File> renamedFiles = new ArrayList<>(mScreenshotFiles.size());
+            for (File oldFile : mScreenshotFiles) {
+                final String oldName = oldFile.getName();
+                final String newName = oldName.replaceFirst(initialName, name);
+                final File newFile;
+                if (!newName.equals(oldName)) {
+                    final File renamedFile = new File(oldFile.getParentFile(), newName);
+                    Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
+                    newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
+                } else {
+                    Log.w(TAG, "Name didn't change: " + oldName);
+                    newFile = oldFile;
+                }
+                if (newFile.length() > 0) {
+                    renamedFiles.add(newFile);
+                } else if (newFile.delete()) {
+                    Log.d(TAG, "screenshot file: " + newFile + " deleted successfully.");
+                }
+            }
+            mScreenshotFiles = renamedFiles;
+        }
+
+        private void deleteEmptyScreenshots() {
+            mScreenshotFiles.removeIf(
+                    file -> {
+                        final long length = file.length();
+                        if (length == 0) {
+                            Log.i(TAG, "Deleting empty screenshot file: " + file);
+                            file.delete();
+                        }
+                        return length == 0;
+                    });
+        }
+
+        /**
+         * Checks if screenshot array is non-empty and returns the first screenshot's path. The
+         * first screenshot is the default screenshot for the bugreport types that take it.
+         */
+        private String getScreenshotForIntent() {
+            if (!mScreenshotFiles.isEmpty()) {
+                final File screenshotFile = mScreenshotFiles.getFirst();
+                return screenshotFile.getAbsolutePath();
+            }
+            return null;
+        }
+    }
+
     @GuardedBy("mLock")
     private void checkProgressUpdatedLocked(BugreportInfo info, int progress) {
         if (progress > CAPPED_PROGRESS) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 3eeaf41..41a00f5 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -873,6 +873,9 @@
                     ) {
                         // Raise closing task to "above" layer so it isn't covered.
                         t.setLayer(target.leash, aboveLayers - i)
+                    } else if (TransitionUtil.isOpeningType(change.mode)) {
+                        // Put into the "below" layer space.
+                        t.setLayer(target.leash, belowLayers - i)
                     }
                 } else if (TransitionInfo.isIndependent(change, info)) {
                     // Root tasks
@@ -1153,7 +1156,7 @@
                 // If a [controller.windowAnimatorState] exists, treat this like a takeover.
                 takeOverAnimationInternal(
                     window,
-                    startWindowStates = null,
+                    startWindowState = null,
                     startTransaction = null,
                     callback,
                 )
@@ -1168,22 +1171,23 @@
             callback: IRemoteAnimationFinishedCallback?,
         ) {
             val window = setUpAnimation(apps, callback) ?: return
-            takeOverAnimationInternal(window, startWindowStates, startTransaction, callback)
+            val startWindowState = startWindowStates[apps!!.indexOf(window)]
+            takeOverAnimationInternal(window, startWindowState, startTransaction, callback)
         }
 
         private fun takeOverAnimationInternal(
             window: RemoteAnimationTarget,
-            startWindowStates: Array<WindowAnimationState>?,
+            startWindowState: WindowAnimationState?,
             startTransaction: SurfaceControl.Transaction?,
             callback: IRemoteAnimationFinishedCallback?,
         ) {
             val useSpring =
-                !controller.isLaunching && startWindowStates != null && startTransaction != null
+                !controller.isLaunching && startWindowState != null && startTransaction != null
             startAnimation(
                 window,
                 navigationBar = null,
                 useSpring,
-                startWindowStates,
+                startWindowState,
                 startTransaction,
                 callback,
             )
@@ -1293,7 +1297,7 @@
             window: RemoteAnimationTarget,
             navigationBar: RemoteAnimationTarget? = null,
             useSpring: Boolean = false,
-            startingWindowStates: Array<WindowAnimationState>? = null,
+            startingWindowState: WindowAnimationState? = null,
             startTransaction: SurfaceControl.Transaction? = null,
             iCallback: IRemoteAnimationFinishedCallback? = null,
         ) {
@@ -1339,6 +1343,7 @@
 
             val isExpandingFullyAbove =
                 transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState)
+            val windowState = startingWindowState ?: controller.windowAnimatorState
 
             // We animate the opening window and delegate the view expansion to [this.controller].
             val delegate = this.controller
@@ -1361,18 +1366,6 @@
                                 }
                         }
 
-                        // The states are sorted matching the changes inside the transition info.
-                        // Using this info, the RemoteAnimationTargets are created, with their
-                        // prefixOrderIndex fields in reverse order to that of changes. To extract
-                        // the right state, we need to invert again.
-                        val windowState =
-                            if (startingWindowStates != null) {
-                                startingWindowStates[
-                                    startingWindowStates.size - window.prefixOrderIndex]
-                            } else {
-                                controller.windowAnimatorState
-                            }
-
                         // TODO(b/323863002): use the timestamp and velocity to update the initial
                         //   position.
                         val bounds = windowState?.bounds
@@ -1461,12 +1454,6 @@
                         delegate.onTransitionAnimationProgress(state, progress, linearProgress)
                     }
                 }
-            val windowState =
-                if (startingWindowStates != null) {
-                    startingWindowStates[startingWindowStates.size - window.prefixOrderIndex]
-                } else {
-                    controller.windowAnimatorState
-                }
             val velocityPxPerS =
                 if (longLivedReturnAnimationsEnabled() && windowState?.velocityPxPerMs != null) {
                     val xVelocityPxPerS = windowState.velocityPxPerMs.x * 1000
@@ -1485,6 +1472,7 @@
                     fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
                     drawHole = !controller.isBelowAnimatingWindow,
                     startVelocity = velocityPxPerS,
+                    startFrameTime = windowState?.timestamp ?: -1,
                 )
         }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index e2bc409..4e889e9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -27,6 +27,8 @@
 import android.util.FloatProperty
 import android.util.Log
 import android.util.MathUtils
+import android.util.TimeUtils
+import android.view.Choreographer
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
@@ -366,6 +368,7 @@
         @get:VisibleForTesting val springY: SpringAnimation,
         @get:VisibleForTesting val springScale: SpringAnimation,
         private val springState: SpringState,
+        private val startFrameTime: Long,
         private val onAnimationStart: Runnable,
     ) : Animation {
         @get:VisibleForTesting
@@ -374,6 +377,42 @@
 
         override fun start() {
             onAnimationStart.run()
+
+            // If no start frame time is provided, we start the springs normally.
+            if (startFrameTime < 0) {
+                startSprings()
+                return
+            }
+
+            // This function is not guaranteed to be called inside a frame. We try to access the
+            // frame time immediately, but if we're not inside a frame this will throw an exception.
+            // We must then post a callback to be run at the beginning of the next frame.
+            try {
+                initAndStartSprings(Choreographer.getInstance().frameTime)
+            } catch (_: IllegalStateException) {
+                Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
+                    initAndStartSprings(frameTimeNanos / TimeUtils.NANOS_PER_MS)
+                }
+            }
+        }
+
+        private fun initAndStartSprings(frameTime: Long) {
+            // Initialize the spring as if it had started at the time that its start state
+            // was created.
+            springX.doAnimationFrame(startFrameTime)
+            springY.doAnimationFrame(startFrameTime)
+            springScale.doAnimationFrame(startFrameTime)
+            // Move the spring time forward to the current frame, so it updates its internal state
+            // following the initial momentum over the elapsed time.
+            springX.doAnimationFrame(frameTime)
+            springY.doAnimationFrame(frameTime)
+            springScale.doAnimationFrame(frameTime)
+            // Actually start the spring. We do this after the previous calls because the framework
+            // doesn't like it when you call doAnimationFrame() after start() with an earlier time.
+            startSprings()
+        }
+
+        private fun startSprings() {
             springX.start()
             springY.start()
             springScale.start()
@@ -471,7 +510,9 @@
      * is true.
      *
      * If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation
-     * using it for the initial momentum will be used instead of the default interpolators.
+     * using it for the initial momentum will be used instead of the default interpolators. In this
+     * case, [startFrameTime] (if non-negative) represents the frame time at which the springs
+     * should be started.
      */
     fun startAnimation(
         controller: Controller,
@@ -480,6 +521,7 @@
         fadeWindowBackgroundLayer: Boolean = true,
         drawHole: Boolean = false,
         startVelocity: PointF? = null,
+        startFrameTime: Long = -1,
     ): Animation {
         if (!controller.isLaunching) assertReturnAnimations()
         if (startVelocity != null) assertLongLivedReturnAnimations()
@@ -502,6 +544,7 @@
                 fadeWindowBackgroundLayer,
                 drawHole,
                 startVelocity,
+                startFrameTime,
             )
             .apply { start() }
     }
@@ -515,6 +558,7 @@
         fadeWindowBackgroundLayer: Boolean = true,
         drawHole: Boolean = false,
         startVelocity: PointF? = null,
+        startFrameTime: Long = -1,
     ): Animation {
         val transitionContainer = controller.transitionContainer
         val transitionContainerOverlay = transitionContainer.overlay
@@ -537,6 +581,7 @@
                 startState,
                 endState,
                 startVelocity,
+                startFrameTime,
                 windowBackgroundLayer,
                 transitionContainer,
                 transitionContainerOverlay,
@@ -722,6 +767,7 @@
         startState: State,
         endState: State,
         startVelocity: PointF,
+        startFrameTime: Long,
         windowBackgroundLayer: GradientDrawable,
         transitionContainer: View,
         transitionContainerOverlay: ViewGroupOverlay,
@@ -912,7 +958,7 @@
                     }
                 }
 
-        return MultiSpringAnimation(springX, springY, springScale, springState) {
+        return MultiSpringAnimation(springX, springY, springScale, springState, startFrameTime) {
             onAnimationStart(
                 controller,
                 isExpandingFullyAbove,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
new file mode 100644
index 0000000..58b8836
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -0,0 +1,484 @@
+/*
+ * 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.compose.gesture
+
+import androidx.compose.foundation.OverscrollEffect
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
+import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
+import androidx.compose.foundation.gestures.horizontalDrag
+import androidx.compose.foundation.gestures.verticalDrag
+import androidx.compose.foundation.overscroll
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerId
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
+import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.input.pointer.util.addPointerInputChange
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.util.fastAny
+import com.android.compose.modifiers.thenIf
+import kotlin.math.sign
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.launch
+
+/**
+ * A draggable that plays nicely with the nested scroll mechanism.
+ *
+ * This can be used whenever you need a draggable inside a scrollable or a draggable that contains a
+ * scrollable.
+ */
+interface NestedDraggable {
+    /**
+     * Called when a drag is started in the given [position] (*before* dragging the touch slop) and
+     * in the direction given by [sign].
+     */
+    fun onDragStarted(position: Offset, sign: Float): Controller
+
+    /**
+     * Whether this draggable should consume any scroll amount with the given [sign] coming from a
+     * nested scrollable.
+     *
+     * This is called whenever a nested scrollable does not consume some scroll amount. If this
+     * returns `true`, then [onDragStarted] will be called and this draggable will have priority and
+     * consume all future events during preScroll until the nested scroll is finished.
+     */
+    fun shouldConsumeNestedScroll(sign: Float): Boolean
+
+    interface Controller {
+        /**
+         * Drag by [delta] pixels.
+         *
+         * @return the consumed [delta]. Any non-consumed delta will be dispatched to the next
+         *   nested scroll connection to be consumed by any composable above in the hierarchy. If
+         *   the drag was performed on this draggable directly (instead of on a nested scrollable),
+         *   any remaining delta will be used to overscroll this draggable.
+         */
+        fun onDrag(delta: Float): Float
+
+        /**
+         * Stop the current drag with the given [velocity].
+         *
+         * @return the consumed [velocity]. Any non-consumed velocity will be dispatched to the next
+         *   nested scroll connection to be consumed by any composable above in the hierarchy. If
+         *   the drag was performed on this draggable directly (instead of on a nested scrollable),
+         *   any remaining velocity will be used to animate the overscroll of this draggable.
+         */
+        suspend fun onDragStopped(velocity: Float): Float
+    }
+}
+
+/**
+ * A draggable that supports nested scrolling and overscroll effects.
+ *
+ * @see NestedDraggable
+ */
+fun Modifier.nestedDraggable(
+    draggable: NestedDraggable,
+    orientation: Orientation,
+    overscrollEffect: OverscrollEffect? = null,
+): Modifier {
+    return this.thenIf(overscrollEffect != null) { Modifier.overscroll(overscrollEffect) }
+        .then(NestedDraggableElement(draggable, orientation, overscrollEffect))
+}
+
+private data class NestedDraggableElement(
+    private val draggable: NestedDraggable,
+    private val orientation: Orientation,
+    private val overscrollEffect: OverscrollEffect?,
+) : ModifierNodeElement<NestedDraggableNode>() {
+    override fun create(): NestedDraggableNode {
+        return NestedDraggableNode(draggable, orientation, overscrollEffect)
+    }
+
+    override fun update(node: NestedDraggableNode) {
+        node.update(draggable, orientation, overscrollEffect)
+    }
+}
+
+private class NestedDraggableNode(
+    private var draggable: NestedDraggable,
+    override var orientation: Orientation,
+    private var overscrollEffect: OverscrollEffect?,
+) :
+    DelegatingNode(),
+    PointerInputModifierNode,
+    NestedScrollConnection,
+    CompositionLocalConsumerModifierNode,
+    OrientationAware {
+    private val nestedScrollDispatcher = NestedScrollDispatcher()
+    private var trackDownPositionDelegate: SuspendingPointerInputModifierNode? = null
+        set(value) {
+            field?.let { undelegate(it) }
+            field = value?.also { delegate(it) }
+        }
+
+    private var detectDragsDelegate: SuspendingPointerInputModifierNode? = null
+        set(value) {
+            field?.let { undelegate(it) }
+            field = value?.also { delegate(it) }
+        }
+
+    /** The controller created by the nested scroll logic (and *not* the drag logic). */
+    private var nestedScrollController: WrappedController? = null
+        set(value) {
+            field?.ensureOnDragStoppedIsCalled()
+            field = value
+        }
+
+    /**
+     * The last pointer which was the first down since the last time all pointers were up.
+     *
+     * This is use to track the started position of a drag started on a nested scrollable.
+     */
+    private var lastFirstDown: Offset? = null
+
+    init {
+        delegate(nestedScrollModifierNode(this, nestedScrollDispatcher))
+    }
+
+    override fun onDetach() {
+        nestedScrollController?.ensureOnDragStoppedIsCalled()
+    }
+
+    fun update(
+        draggable: NestedDraggable,
+        orientation: Orientation,
+        overscrollEffect: OverscrollEffect?,
+    ) {
+        this.draggable = draggable
+        this.orientation = orientation
+        this.overscrollEffect = overscrollEffect
+
+        trackDownPositionDelegate?.resetPointerInputHandler()
+        detectDragsDelegate?.resetPointerInputHandler()
+        nestedScrollController?.ensureOnDragStoppedIsCalled()
+    }
+
+    override fun onPointerEvent(
+        pointerEvent: PointerEvent,
+        pass: PointerEventPass,
+        bounds: IntSize,
+    ) {
+        if (trackDownPositionDelegate == null) {
+            check(detectDragsDelegate == null)
+            trackDownPositionDelegate = SuspendingPointerInputModifierNode { trackDownPosition() }
+            detectDragsDelegate = SuspendingPointerInputModifierNode { detectDrags() }
+        }
+
+        checkNotNull(trackDownPositionDelegate).onPointerEvent(pointerEvent, pass, bounds)
+        checkNotNull(detectDragsDelegate).onPointerEvent(pointerEvent, pass, bounds)
+    }
+
+    override fun onCancelPointerInput() {
+        trackDownPositionDelegate?.onCancelPointerInput()
+        detectDragsDelegate?.onCancelPointerInput()
+    }
+
+    /*
+     * ======================================
+     * ===== Pointer input (drag) logic =====
+     * ======================================
+     */
+
+    private suspend fun PointerInputScope.detectDrags() {
+        // Lazily create the velocity tracker when the pointer input restarts.
+        val velocityTracker = VelocityTracker()
+
+        awaitEachGesture {
+            val down = awaitFirstDown(requireUnconsumed = false)
+            var overSlop = 0f
+            val onTouchSlopReached = { change: PointerInputChange, over: Float ->
+                change.consume()
+                overSlop = over
+            }
+
+            suspend fun AwaitPointerEventScope.awaitTouchSlopOrCancellation(
+                pointerId: PointerId
+            ): PointerInputChange? {
+                return when (orientation) {
+                    Orientation.Horizontal ->
+                        awaitHorizontalTouchSlopOrCancellation(pointerId, onTouchSlopReached)
+                    Orientation.Vertical ->
+                        awaitVerticalTouchSlopOrCancellation(pointerId, onTouchSlopReached)
+                }
+            }
+
+            var drag = awaitTouchSlopOrCancellation(down.id)
+
+            // We try to pick-up the drag gesture in case the touch slop swipe was consumed by a
+            // nested scrollable child that disappeared.
+            // This was copied from http://shortn/_10L8U02IoL.
+            // TODO(b/380838584): Reuse detect(Horizontal|Vertical)DragGestures() instead.
+            while (drag == null && currentEvent.changes.fastAny { it.pressed }) {
+                var event: PointerEvent
+                do {
+                    event = awaitPointerEvent()
+                } while (
+                    event.changes.fastAny { it.isConsumed } && event.changes.fastAny { it.pressed }
+                )
+
+                // An event was not consumed and there's still a pointer in the screen.
+                if (event.changes.fastAny { it.pressed }) {
+                    // Await touch slop again, using the initial down as starting point.
+                    // For most cases this should return immediately since we probably moved
+                    // far enough from the initial down event.
+                    drag = awaitTouchSlopOrCancellation(down.id)
+                }
+            }
+
+            if (drag != null) {
+                velocityTracker.resetTracking()
+
+                val sign = (drag.position - down.position).toFloat().sign
+                val wrappedController =
+                    WrappedController(coroutineScope, draggable.onDragStarted(down.position, sign))
+                if (overSlop != 0f) {
+                    onDrag(wrappedController, drag, overSlop, velocityTracker)
+                }
+
+                // If a drag was started, we cancel any other drag started by a nested scrollable.
+                //
+                // Note: we cancel the nested drag here *after* starting the new drag so that in the
+                // STL case, the cancelled drag will not change the current scene of the STL.
+                nestedScrollController?.ensureOnDragStoppedIsCalled()
+
+                val isSuccessful =
+                    try {
+                        val onDrag = { change: PointerInputChange ->
+                            onDrag(
+                                wrappedController,
+                                change,
+                                change.positionChange().toFloat(),
+                                velocityTracker,
+                            )
+                            change.consume()
+                        }
+
+                        when (orientation) {
+                            Orientation.Horizontal -> horizontalDrag(drag.id, onDrag)
+                            Orientation.Vertical -> verticalDrag(drag.id, onDrag)
+                        }
+                    } catch (t: Throwable) {
+                        wrappedController.ensureOnDragStoppedIsCalled()
+                        throw t
+                    }
+
+                if (isSuccessful) {
+                    val maxVelocity = currentValueOf(LocalViewConfiguration).maximumFlingVelocity
+                    val velocity =
+                        velocityTracker
+                            .calculateVelocity(Velocity(maxVelocity, maxVelocity))
+                            .toFloat()
+                    onDragStopped(wrappedController, velocity)
+                } else {
+                    onDragStopped(wrappedController, velocity = 0f)
+                }
+            }
+        }
+    }
+
+    private fun onDrag(
+        controller: NestedDraggable.Controller,
+        change: PointerInputChange,
+        delta: Float,
+        velocityTracker: VelocityTracker,
+    ) {
+        velocityTracker.addPointerInputChange(change)
+
+        scrollWithOverscroll(delta) { deltaFromOverscroll ->
+            scrollWithNestedScroll(deltaFromOverscroll) { deltaFromNestedScroll ->
+                controller.onDrag(deltaFromNestedScroll)
+            }
+        }
+    }
+
+    private fun onDragStopped(controller: WrappedController, velocity: Float) {
+        coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
+            try {
+                flingWithOverscroll(velocity) { velocityFromOverscroll ->
+                    flingWithNestedScroll(velocityFromOverscroll) { velocityFromNestedScroll ->
+                        controller.onDragStopped(velocityFromNestedScroll)
+                    }
+                }
+            } finally {
+                controller.ensureOnDragStoppedIsCalled()
+            }
+        }
+    }
+
+    private fun scrollWithOverscroll(delta: Float, performScroll: (Float) -> Float): Float {
+        val effect = overscrollEffect
+        return if (effect != null) {
+            effect
+                .applyToScroll(delta.toOffset(), source = NestedScrollSource.UserInput) {
+                    performScroll(it.toFloat()).toOffset()
+                }
+                .toFloat()
+        } else {
+            performScroll(delta)
+        }
+    }
+
+    private fun scrollWithNestedScroll(delta: Float, performScroll: (Float) -> Float): Float {
+        val preConsumed =
+            nestedScrollDispatcher
+                .dispatchPreScroll(
+                    available = delta.toOffset(),
+                    source = NestedScrollSource.UserInput,
+                )
+                .toFloat()
+        val available = delta - preConsumed
+        val consumed = performScroll(available)
+        val left = available - consumed
+        val postConsumed =
+            nestedScrollDispatcher
+                .dispatchPostScroll(
+                    consumed = (preConsumed + consumed).toOffset(),
+                    available = left.toOffset(),
+                    source = NestedScrollSource.UserInput,
+                )
+                .toFloat()
+        return consumed + preConsumed + postConsumed
+    }
+
+    private suspend fun flingWithOverscroll(
+        velocity: Float,
+        performFling: suspend (Float) -> Float,
+    ) {
+        val effect = overscrollEffect
+        if (effect != null) {
+            effect.applyToFling(velocity.toVelocity()) { performFling(it.toFloat()).toVelocity() }
+        } else {
+            performFling(velocity)
+        }
+    }
+
+    private suspend fun flingWithNestedScroll(
+        velocity: Float,
+        performFling: suspend (Float) -> Float,
+    ): Float {
+        val preConsumed = nestedScrollDispatcher.dispatchPreFling(available = velocity.toVelocity())
+        val available = velocity - preConsumed.toFloat()
+        val consumed = performFling(available)
+        val left = available - consumed
+        return nestedScrollDispatcher
+            .dispatchPostFling(
+                consumed = consumed.toVelocity() + preConsumed,
+                available = left.toVelocity(),
+            )
+            .toFloat()
+    }
+
+    /*
+     * ===============================
+     * ===== Nested scroll logic =====
+     * ===============================
+     */
+
+    private suspend fun PointerInputScope.trackDownPosition() {
+        awaitEachGesture { lastFirstDown = awaitFirstDown(requireUnconsumed = false).position }
+    }
+
+    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+        val controller = nestedScrollController ?: return Offset.Zero
+        val consumed = controller.onDrag(available.toFloat())
+        return consumed.toOffset()
+    }
+
+    override fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource,
+    ): Offset {
+        if (source == NestedScrollSource.SideEffect) {
+            check(nestedScrollController == null)
+            return Offset.Zero
+        }
+
+        val offset = available.toFloat()
+        if (offset == 0f) {
+            return Offset.Zero
+        }
+
+        val sign = offset.sign
+        if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) {
+            val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" }
+            nestedScrollController =
+                WrappedController(coroutineScope, draggable.onDragStarted(startedPosition, sign))
+        }
+
+        val controller = nestedScrollController ?: return Offset.Zero
+        return controller.onDrag(offset).toOffset()
+    }
+
+    override suspend fun onPreFling(available: Velocity): Velocity {
+        val controller = nestedScrollController ?: return Velocity.Zero
+        nestedScrollController = null
+
+        val consumed = controller.onDragStopped(available.toFloat())
+        return consumed.toVelocity()
+    }
+}
+
+/**
+ * A controller that wraps [delegate] and can be used to ensure that [onDragStopped] is called, but
+ * not more than once.
+ */
+private class WrappedController(
+    private val coroutineScope: CoroutineScope,
+    private val delegate: NestedDraggable.Controller,
+) : NestedDraggable.Controller by delegate {
+    private var onDragStoppedCalled = false
+
+    override fun onDrag(delta: Float): Float {
+        if (onDragStoppedCalled) return 0f
+        return delegate.onDrag(delta)
+    }
+
+    override suspend fun onDragStopped(velocity: Float): Float {
+        if (onDragStoppedCalled) return 0f
+        onDragStoppedCalled = true
+        return delegate.onDragStopped(velocity)
+    }
+
+    fun ensureOnDragStoppedIsCalled() {
+        // Start with UNDISPATCHED so that onDragStopped() is always run until its first suspension
+        // point, even if coroutineScope is cancelled.
+        coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { onDragStopped(velocity = 0f) }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/OrientationAware.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/OrientationAware.kt
new file mode 100644
index 0000000..6e91727
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/OrientationAware.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.compose.gesture
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Velocity
+
+/**
+ * An interface to conveniently convert a [Float] to and from an [Offset] or a [Velocity] given an
+ * [orientation].
+ */
+interface OrientationAware {
+    val orientation: Orientation
+
+    fun Float.toOffset(): Offset {
+        return when (orientation) {
+            Orientation.Horizontal -> Offset(x = this, y = 0f)
+            Orientation.Vertical -> Offset(x = 0f, y = this)
+        }
+    }
+
+    fun Float.toVelocity(): Velocity {
+        return when (orientation) {
+            Orientation.Horizontal -> Velocity(x = this, y = 0f)
+            Orientation.Vertical -> Velocity(x = 0f, y = this)
+        }
+    }
+
+    fun Offset.toFloat(): Float {
+        return when (orientation) {
+            Orientation.Horizontal -> this.x
+            Orientation.Vertical -> this.y
+        }
+    }
+
+    fun Velocity.toFloat(): Float {
+        return when (orientation) {
+            Orientation.Horizontal -> this.x
+            Orientation.Vertical -> this.y
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
new file mode 100644
index 0000000..f8561b8
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -0,0 +1,404 @@
+/*
+ * 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.compose.gesture
+
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+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.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.unit.Velocity
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.awaitCancellation
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class NestedDraggableTest(override val orientation: Orientation) : OrientationAware {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun orientations() = listOf(Orientation.Horizontal, Orientation.Vertical)
+    }
+
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun simpleDrag() {
+        val draggable = TestDraggable()
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+            }
+
+        assertThat(draggable.onDragStartedCalled).isFalse()
+        assertThat(draggable.onDragCalled).isFalse()
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        var rootCenter = Offset.Zero
+        rule.onRoot().performTouchInput {
+            rootCenter = center
+            down(center)
+            moveBy((touchSlop + 10f).toOffset())
+        }
+
+        assertThat(draggable.onDragStartedCalled).isTrue()
+        assertThat(draggable.onDragCalled).isTrue()
+        assertThat(draggable.onDragDelta).isEqualTo(10f)
+        assertThat(draggable.onDragStartedPosition).isEqualTo(rootCenter)
+        assertThat(draggable.onDragStartedSign).isEqualTo(1f)
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        rule.onRoot().performTouchInput { moveBy(20f.toOffset()) }
+
+        assertThat(draggable.onDragDelta).isEqualTo(30f)
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        rule.onRoot().performTouchInput {
+            moveBy((-15f).toOffset())
+            up()
+        }
+
+        assertThat(draggable.onDragDelta).isEqualTo(15f)
+        assertThat(draggable.onDragStoppedCalled).isTrue()
+    }
+
+    @Test
+    fun nestedScrollable() {
+        val draggable = TestDraggable()
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(
+                    Modifier.fillMaxSize()
+                        .nestedDraggable(draggable, orientation)
+                        .nestedScrollable(rememberScrollState())
+                )
+            }
+
+        assertThat(draggable.onDragStartedCalled).isFalse()
+        assertThat(draggable.onDragCalled).isFalse()
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        var rootCenter = Offset.Zero
+        rule.onRoot().performTouchInput {
+            rootCenter = center
+            down(center)
+            moveBy((-touchSlop - 10f).toOffset())
+        }
+
+        assertThat(draggable.onDragStartedCalled).isTrue()
+        assertThat(draggable.onDragCalled).isTrue()
+        assertThat(draggable.onDragDelta).isEqualTo(-10f)
+        assertThat(draggable.onDragStartedPosition).isEqualTo(rootCenter)
+        assertThat(draggable.onDragStartedSign).isEqualTo(-1f)
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        rule.onRoot().performTouchInput { moveBy((-20f).toOffset()) }
+
+        assertThat(draggable.onDragStartedCalled).isTrue()
+        assertThat(draggable.onDragCalled).isTrue()
+        assertThat(draggable.onDragDelta).isEqualTo(-30f)
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        rule.onRoot().performTouchInput {
+            moveBy(15f.toOffset())
+            up()
+        }
+
+        assertThat(draggable.onDragStartedCalled).isTrue()
+        assertThat(draggable.onDragCalled).isTrue()
+        assertThat(draggable.onDragDelta).isEqualTo(-15f)
+        assertThat(draggable.onDragStoppedCalled).isTrue()
+    }
+
+    @Test
+    fun onDragStoppedIsCalledWhenDraggableIsUpdatedAndReset() {
+        val draggable = TestDraggable()
+        var orientation by mutableStateOf(orientation)
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+            }
+
+        assertThat(draggable.onDragStartedCalled).isFalse()
+
+        rule.onRoot().performTouchInput {
+            down(center)
+            moveBy(touchSlop.toOffset())
+        }
+
+        assertThat(draggable.onDragStartedCalled).isTrue()
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        orientation =
+            when (orientation) {
+                Orientation.Horizontal -> Orientation.Vertical
+                Orientation.Vertical -> Orientation.Horizontal
+            }
+        rule.waitForIdle()
+        assertThat(draggable.onDragStoppedCalled).isTrue()
+    }
+
+    @Test
+    fun onDragStoppedIsCalledWhenDraggableIsUpdatedAndReset_nestedScroll() {
+        val draggable = TestDraggable()
+        var orientation by mutableStateOf(orientation)
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(
+                    Modifier.fillMaxSize()
+                        .nestedDraggable(draggable, orientation)
+                        .nestedScrollable(rememberScrollState())
+                )
+            }
+
+        assertThat(draggable.onDragStartedCalled).isFalse()
+
+        rule.onRoot().performTouchInput {
+            down(center)
+            moveBy((touchSlop + 1f).toOffset())
+        }
+
+        assertThat(draggable.onDragStartedCalled).isTrue()
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        orientation =
+            when (orientation) {
+                Orientation.Horizontal -> Orientation.Vertical
+                Orientation.Vertical -> Orientation.Horizontal
+            }
+        rule.waitForIdle()
+        assertThat(draggable.onDragStoppedCalled).isTrue()
+    }
+
+    @Test
+    fun onDragStoppedIsCalledWhenDraggableIsRemovedDuringDrag() {
+        val draggable = TestDraggable()
+        var composeContent by mutableStateOf(true)
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                if (composeContent) {
+                    Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+                }
+            }
+
+        assertThat(draggable.onDragStartedCalled).isFalse()
+
+        rule.onRoot().performTouchInput {
+            down(center)
+            moveBy(touchSlop.toOffset())
+        }
+
+        assertThat(draggable.onDragStartedCalled).isTrue()
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        composeContent = false
+        rule.waitForIdle()
+        assertThat(draggable.onDragStoppedCalled).isTrue()
+    }
+
+    @Test
+    fun onDragStoppedIsCalledWhenDraggableIsRemovedDuringDrag_nestedScroll() {
+        val draggable = TestDraggable()
+        var composeContent by mutableStateOf(true)
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                if (composeContent) {
+                    Box(
+                        Modifier.fillMaxSize()
+                            .nestedDraggable(draggable, orientation)
+                            .nestedScrollable(rememberScrollState())
+                    )
+                }
+            }
+
+        assertThat(draggable.onDragStartedCalled).isFalse()
+
+        rule.onRoot().performTouchInput {
+            down(center)
+            moveBy((touchSlop + 1f).toOffset())
+        }
+
+        assertThat(draggable.onDragStartedCalled).isTrue()
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        composeContent = false
+        rule.waitForIdle()
+        assertThat(draggable.onDragStoppedCalled).isTrue()
+    }
+
+    @Test
+    fun onDragStoppedIsCalledWhenDraggableIsRemovedDuringFling() {
+        val draggable = TestDraggable()
+        var composeContent by mutableStateOf(true)
+        var preFlingCalled = false
+        rule.setContent {
+            if (composeContent) {
+                Box(
+                    Modifier.fillMaxSize()
+                        // This nested scroll connection indefinitely suspends on pre fling, so that
+                        // we can emulate what happens when the draggable is removed from
+                        // composition while the pre-fling happens and onDragStopped() was not
+                        // called yet.
+                        .nestedScroll(
+                            remember {
+                                object : NestedScrollConnection {
+                                    override suspend fun onPreFling(available: Velocity): Velocity {
+                                        preFlingCalled = true
+                                        awaitCancellation()
+                                    }
+                                }
+                            }
+                        )
+                        .nestedDraggable(draggable, orientation)
+                )
+            }
+        }
+
+        assertThat(draggable.onDragStartedCalled).isFalse()
+
+        // Swipe down.
+        rule.onRoot().performTouchInput {
+            when (orientation) {
+                Orientation.Horizontal -> swipeLeft()
+                Orientation.Vertical -> swipeDown()
+            }
+        }
+
+        assertThat(draggable.onDragStartedCalled).isTrue()
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+        assertThat(preFlingCalled).isTrue()
+
+        composeContent = false
+        rule.waitForIdle()
+        assertThat(draggable.onDragStoppedCalled).isTrue()
+    }
+
+    @Test
+    @Ignore("b/303224944#comment22")
+    fun onDragStoppedIsCalledWhenNestedScrollableIsRemoved() {
+        val draggable = TestDraggable()
+        var composeNestedScrollable by mutableStateOf(true)
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(
+                    Modifier.fillMaxSize()
+                        .nestedDraggable(draggable, orientation)
+                        .then(
+                            if (composeNestedScrollable) {
+                                Modifier.nestedScrollable(rememberScrollState())
+                            } else {
+                                Modifier
+                            }
+                        )
+                )
+            }
+
+        assertThat(draggable.onDragStartedCalled).isFalse()
+
+        rule.onRoot().performTouchInput {
+            down(center)
+            moveBy((touchSlop + 1f).toOffset())
+        }
+
+        assertThat(draggable.onDragStartedCalled).isTrue()
+        assertThat(draggable.onDragStoppedCalled).isFalse()
+
+        composeNestedScrollable = false
+        rule.waitForIdle()
+        assertThat(draggable.onDragStoppedCalled).isTrue()
+    }
+
+    private fun ComposeContentTestRule.setContentWithTouchSlop(
+        content: @Composable () -> Unit
+    ): Float {
+        var touchSlop = 0f
+        setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            content()
+        }
+        return touchSlop
+    }
+
+    private fun Modifier.nestedScrollable(scrollState: ScrollState): Modifier {
+        return when (orientation) {
+            Orientation.Vertical -> verticalScroll(scrollState)
+            Orientation.Horizontal -> horizontalScroll(scrollState)
+        }
+    }
+
+    private class TestDraggable(
+        private val onDragStarted: (Offset, Float) -> Unit = { _, _ -> },
+        private val onDrag: (Float) -> Float = { it },
+        private val onDragStopped: suspend (Float) -> Float = { it },
+        private val shouldConsumeNestedScroll: (Float) -> Boolean = { true },
+    ) : NestedDraggable {
+        var onDragStartedCalled = false
+        var onDragCalled = false
+        var onDragStoppedCalled = false
+
+        var onDragStartedPosition = Offset.Zero
+        var onDragStartedSign = 0f
+        var onDragDelta = 0f
+
+        override fun onDragStarted(position: Offset, sign: Float): NestedDraggable.Controller {
+            onDragStartedCalled = true
+            onDragStartedPosition = position
+            onDragStartedSign = sign
+            onDragDelta = 0f
+
+            onDragStarted.invoke(position, sign)
+            return object : NestedDraggable.Controller {
+                override fun onDrag(delta: Float): Float {
+                    onDragCalled = true
+                    onDragDelta += delta
+                    return onDrag.invoke(delta)
+                }
+
+                override suspend fun onDragStopped(velocity: Float): Float {
+                    onDragStoppedCalled = true
+                    return onDragStopped.invoke(velocity)
+                }
+            }
+        }
+
+        override fun shouldConsumeNestedScroll(sign: Float): Boolean {
+            return shouldConsumeNestedScroll.invoke(sign)
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 6d03118..0e35e1d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -45,6 +45,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Expandable
+import com.android.systemui.Flags
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
@@ -56,7 +57,7 @@
 /** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
 class ButtonComponent(
     private val viewModelFlow: StateFlow<ButtonViewModel?>,
-    private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit
+    private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit,
 ) : ComposeVolumePanelUiComponent {
 
     @Composable
@@ -84,14 +85,26 @@
                         },
                     color =
                         if (viewModel.isActive) {
-                            MaterialTheme.colorScheme.tertiaryContainer
+                            if (Flags.volumeRedesign()) {
+                                MaterialTheme.colorScheme.primary
+                            } else {
+                                MaterialTheme.colorScheme.tertiaryContainer
+                            }
                         } else {
-                            MaterialTheme.colorScheme.surface
+                            if (Flags.volumeRedesign()) {
+                                MaterialTheme.colorScheme.surfaceContainerHigh
+                            } else {
+                                MaterialTheme.colorScheme.surface
+                            }
                         },
                     shape = RoundedCornerShape(20.dp),
                     contentColor =
                         if (viewModel.isActive) {
-                            MaterialTheme.colorScheme.onTertiaryContainer
+                            if (Flags.volumeRedesign()) {
+                                MaterialTheme.colorScheme.onPrimary
+                            } else {
+                                MaterialTheme.colorScheme.onTertiaryContainer
+                            }
                         } else {
                             MaterialTheme.colorScheme.onSurface
                         },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index bb2daec..2cd7304 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -42,6 +42,7 @@
 import androidx.compose.ui.state.ToggleableState
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.Flags
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
 import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
@@ -51,7 +52,7 @@
 /** [ComposeVolumePanelUiComponent] implementing a toggleable button from a bottom row. */
 class ToggleButtonComponent(
     private val viewModelFlow: StateFlow<ButtonViewModel?>,
-    private val onCheckedChange: (isChecked: Boolean) -> Unit
+    private val onCheckedChange: (isChecked: Boolean) -> Unit,
 ) : ComposeVolumePanelUiComponent {
 
     @Composable
@@ -68,15 +69,29 @@
             BottomComponentButtonSurface {
                 val colors =
                     if (viewModel.isActive) {
-                        ButtonDefaults.buttonColors(
-                            containerColor = MaterialTheme.colorScheme.tertiaryContainer,
-                            contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
-                        )
+                        if (Flags.volumeRedesign()) {
+                            ButtonDefaults.buttonColors(
+                                containerColor = MaterialTheme.colorScheme.primary,
+                                contentColor = MaterialTheme.colorScheme.onPrimary,
+                            )
+                        } else {
+                            ButtonDefaults.buttonColors(
+                                containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                                contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+                            )
+                        }
                     } else {
-                        ButtonDefaults.buttonColors(
-                            containerColor = Color.Transparent,
-                            contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
-                        )
+                        if (Flags.volumeRedesign()) {
+                            ButtonDefaults.buttonColors(
+                                containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
+                                contentColor = MaterialTheme.colorScheme.onSurface,
+                            )
+                        } else {
+                            ButtonDefaults.buttonColors(
+                                containerColor = Color.Transparent,
+                                contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+                            )
+                        }
                     }
                 Button(
                     modifier =
@@ -93,7 +108,7 @@
                     onClick = { onCheckedChange(!viewModel.isActive) },
                     shape = RoundedCornerShape(20.dp),
                     colors = colors,
-                    contentPadding = PaddingValues(0.dp)
+                    contentPadding = PaddingValues(0.dp),
                 ) {
                     Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 581fb9d..25892c5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -37,11 +37,13 @@
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.Role
@@ -51,8 +53,11 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.PlatformIconButton
 import com.android.compose.PlatformSliderColors
 import com.android.compose.modifiers.padding
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
 
@@ -84,7 +89,11 @@
             val sliderPadding by topSliderPadding(isExpandable)
 
             VolumeSlider(
-                modifier = Modifier.padding(end = { sliderPadding.roundToPx() }).fillMaxWidth(),
+                modifier =
+                    Modifier.thenIf(!Flags.volumeRedesign()) {
+                            Modifier.padding(end = { sliderPadding.roundToPx() })
+                        }
+                        .fillMaxWidth(),
                 state = sliderState,
                 onValueChange = { newValue: Float ->
                     sliderViewModel.onValueChanged(sliderState, newValue)
@@ -93,15 +102,29 @@
                 onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
                 hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(),
+                button =
+                    if (Flags.volumeRedesign()) {
+                        {
+                            ExpandButton(
+                                isExpanded = isExpanded,
+                                isExpandable = isExpandable,
+                                onExpandedChanged = onExpandedChanged,
+                            )
+                        }
+                    } else {
+                        null
+                    },
             )
 
-            ExpandButton(
-                modifier = Modifier.align(Alignment.CenterEnd),
-                isExpanded = isExpanded,
-                isExpandable = isExpandable,
-                onExpandedChanged = onExpandedChanged,
-                sliderColors = sliderColors,
-            )
+            if (!Flags.volumeRedesign()) {
+                ExpandButtonLegacy(
+                    modifier = Modifier.align(Alignment.CenterEnd),
+                    isExpanded = isExpanded,
+                    isExpandable = isExpandable,
+                    onExpandedChanged = onExpandedChanged,
+                    sliderColors = sliderColors,
+                )
+            }
         }
         AnimatedVisibility(
             visible = isExpanded || !isExpandable,
@@ -153,7 +176,7 @@
 }
 
 @Composable
-private fun ExpandButton(
+private fun ExpandButtonLegacy(
     isExpanded: Boolean,
     isExpandable: Boolean,
     onExpandedChanged: (Boolean) -> Unit,
@@ -200,6 +223,48 @@
     }
 }
 
+@Composable
+private fun ExpandButton(
+    isExpanded: Boolean,
+    isExpandable: Boolean,
+    onExpandedChanged: (Boolean) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    val expandButtonStateDescription =
+        if (isExpanded) {
+            stringResource(R.string.volume_panel_expanded_sliders)
+        } else {
+            stringResource(R.string.volume_panel_collapsed_sliders)
+        }
+    AnimatedVisibility(
+        modifier = modifier,
+        visible = isExpandable,
+        enter = expandButtonEnterTransition(),
+        exit = expandButtonExitTransition(),
+    ) {
+        PlatformIconButton(
+            modifier =
+                Modifier.size(width = 48.dp, height = 40.dp).semantics {
+                    role = Role.Switch
+                    stateDescription = expandButtonStateDescription
+                },
+            onClick = { onExpandedChanged(!isExpanded) },
+            colors =
+                IconButtonDefaults.iconButtonColors(
+                    containerColor = Color.Transparent,
+                    contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+                ),
+            iconResource =
+                if (isExpanded) {
+                    R.drawable.ic_arrow_down_24dp
+                } else {
+                    R.drawable.ic_arrow_up_24dp
+                },
+            contentDescription = null,
+        )
+    }
+}
+
 private fun enterTransition(index: Int, totalCount: Int): EnterTransition {
     val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0)
     val enterDuration = (EXPAND_DURATION_MILLIS - enterDelay).coerceAtLeast(100)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 97ce429..fa5f72b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -24,9 +24,18 @@
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
@@ -48,6 +57,7 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformSlider
 import com.android.compose.PlatformSliderColors
+import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.compose.modifiers.sysuiResTag
@@ -61,11 +71,104 @@
 fun VolumeSlider(
     state: SliderState,
     onValueChange: (newValue: Float) -> Unit,
-    onValueChangeFinished: (() -> Unit)? = null,
     onIconTapped: () -> Unit,
+    sliderColors: PlatformSliderColors,
     modifier: Modifier = Modifier,
+    hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
+    onValueChangeFinished: (() -> Unit)? = null,
+    button: (@Composable () -> Unit)? = null,
+) {
+    if (!Flags.volumeRedesign()) {
+        LegacyVolumeSlider(
+            state = state,
+            onValueChange = onValueChange,
+            onIconTapped = onIconTapped,
+            sliderColors = sliderColors,
+            onValueChangeFinished = onValueChangeFinished,
+            modifier = modifier,
+            hapticsViewModelFactory = hapticsViewModelFactory,
+        )
+        return
+    }
+
+    val value by valueState(state)
+    Column(modifier) {
+        Row(
+            horizontalArrangement = Arrangement.spacedBy(12.dp),
+            modifier = Modifier.fillMaxWidth(),
+        ) {
+            state.icon?.let {
+                Icon(
+                    icon = it,
+                    tint = MaterialTheme.colorScheme.onSurface,
+                    modifier = Modifier.size(40.dp).padding(8.dp),
+                )
+            }
+            Text(
+                text = state.label,
+                style = MaterialTheme.typography.titleMedium,
+                color = MaterialTheme.colorScheme.onSurface,
+                modifier = Modifier.weight(1f).align(Alignment.CenterVertically),
+            )
+            button?.invoke()
+        }
+        Slider(
+            value = value,
+            valueRange = state.valueRange,
+            onValueChange = onValueChange,
+            onValueChangeFinished = onValueChangeFinished,
+            enabled = state.isEnabled,
+            modifier =
+                Modifier.height(40.dp).sysuiResTag(state.label).clearAndSetSemantics {
+                    if (state.isEnabled) {
+                        contentDescription = state.label
+                        state.a11yClickDescription?.let {
+                            customActions =
+                                listOf(
+                                    CustomAccessibilityAction(it) {
+                                        onIconTapped()
+                                        true
+                                    }
+                                )
+                        }
+
+                        state.a11yStateDescription?.let { stateDescription = it }
+                        progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+                    } else {
+                        disabled()
+                        contentDescription =
+                            state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
+                    }
+                    setProgress { targetValue ->
+                        val targetDirection =
+                            when {
+                                targetValue > value -> 1
+                                targetValue < value -> -1
+                                else -> 0
+                            }
+
+                        val newValue =
+                            (value + targetDirection * state.a11yStep).coerceIn(
+                                state.valueRange.start,
+                                state.valueRange.endInclusive,
+                            )
+                        onValueChange(newValue)
+                        true
+                    }
+                },
+        )
+    }
+}
+
+@Composable
+private fun LegacyVolumeSlider(
+    state: SliderState,
+    onValueChange: (newValue: Float) -> Unit,
+    onIconTapped: () -> Unit,
     sliderColors: PlatformSliderColors,
     hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
+    modifier: Modifier = Modifier,
+    onValueChangeFinished: (() -> Unit)? = null,
 ) {
     val value by valueState(state)
     val interactionSource = remember { MutableInteractionSource() }
@@ -178,7 +281,7 @@
     val shouldSkipAnimation =
         prevState is SliderState.Empty || prevState.isEnabled != state.isEnabled
     val value =
-        if (shouldSkipAnimation) mutableFloatStateOf(state.value)
+        if (shouldSkipAnimation) remember { mutableFloatStateOf(state.value) }
         else animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
     prevState = state
     return value
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
index 4ae4eb8..28226ff 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
@@ -53,7 +53,7 @@
     DisabledMessage,
 }
 
-/** Shows label of the [VolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */
+/** Shows label of the [LegacyVolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */
 @Composable
 fun VolumeSliderContent(
     label: String,
@@ -89,7 +89,7 @@
                 }
             }
         },
-        measurePolicy = VolumeSliderContentMeasurePolicy(isEnabled)
+        measurePolicy = VolumeSliderContentMeasurePolicy(isEnabled),
     )
 }
 
@@ -102,7 +102,7 @@
 
     override fun MeasureScope.measure(
         measurables: List<Measurable>,
-        constraints: Constraints
+        constraints: Constraints,
     ): MeasureResult {
         val labelPlaceable =
             measurables
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 0fc88b2..a4237f3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -225,7 +225,7 @@
     return animateElementValueAsState(value, key, SharedColorType, canOverflow = false)
 }
 
-private object SharedColorType : SharedValueType<Color, ColorDelta> {
+internal object SharedColorType : SharedValueType<Color, ColorDelta> {
     override val unspecifiedValue: Color = Color.Unspecified
     override val zeroDeltaValue: ColorDelta = ColorDelta(0f, 0f, 0f, 0f)
 
@@ -255,17 +255,17 @@
                 alpha = aOklab.alpha + b.alpha * bWeight,
                 colorSpace = ColorSpaces.Oklab,
             )
-            .convert(aOklab.colorSpace)
+            .convert(a.colorSpace)
     }
 }
 
 /**
- * Represents the diff between two colors in the same color space.
+ * Represents the diff between two colors in the Oklab color space.
  *
  * Note: This class is necessary because Color() checks the bounds of its values and UncheckedColor
  * is internal.
  */
-private class ColorDelta(val red: Float, val green: Float, val blue: Float, val alpha: Float)
+internal class ColorDelta(val red: Float, val green: Float, val blue: Float, val alpha: Float)
 
 @Composable
 internal fun <T> animateSharedValueAsState(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 3644b30..2fd1d8d8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -18,7 +18,10 @@
 
 import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.SideEffect
@@ -495,4 +498,13 @@
         assertThat(lastValues[SceneA]).isWithin(0.001f).of(100f)
         assertThat(lastValues[SceneB]).isWithin(0.001f).of(100f)
     }
+
+    @Test
+    fun interpolatedColor() {
+        val a = Color.Red
+        val b = Color.Green
+        val delta = SharedColorType.diff(b, a) // b - a
+        val interpolated = SharedColorType.addWeighted(a, delta, 0.5f) // a + (b - a) * 0.5f
+        rule.setContent { Box(Modifier.fillMaxSize().background(interpolated)) }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 4e64c50..297aee5c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.graphics.Rect
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.view.MotionEvent
 import android.view.Surface
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -24,6 +26,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.authController
+import com.android.systemui.biometrics.fingerprintManager
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
@@ -39,6 +42,8 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
@@ -57,6 +62,8 @@
     private val testScope: TestScope = kosmos.testScope
 
     private val authController: AuthController = kosmos.authController
+    private val fingerprintManager: FingerprintManager = kosmos.fingerprintManager
+    @Mock private lateinit var fingerprintSensorProperties: FingerprintSensorPropertiesInternal
     @Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
 
     @Mock private lateinit var udfpsOverlayParams: UdfpsOverlayParams
@@ -122,6 +129,20 @@
             context.orCreateTestableResources.removeOverride(R.dimen.pixel_pitch)
         }
 
+    @Test
+    fun testSetIgnoreDisplayTouches() =
+        testScope.runTest {
+            createUdfpsOverlayInteractor()
+            whenever(authController.isUdfpsSupported).thenReturn(true)
+            whenever(authController.udfpsProps).thenReturn(listOf(fingerprintSensorProperties))
+
+            underTest.setHandleTouches(false)
+            verify(fingerprintManager).setIgnoreDisplayTouches(anyLong(), anyInt(), eq(true))
+
+            underTest.setHandleTouches(true)
+            verify(fingerprintManager).setIgnoreDisplayTouches(anyLong(), anyInt(), eq(false))
+        }
+
     private fun createUdfpsOverlayInteractor() {
         underTest = kosmos.udfpsOverlayInteractor
         testScope.runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index e079619..635f8026 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -995,12 +995,12 @@
 
             kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
             val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
-            sendSteps(sendStep1)
-            kosmos.setSceneTransition(Idle(Scenes.Gone))
             val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+            sendSteps(sendStep1, sendStep2)
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
             val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
             val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
-            sendSteps(sendStep2, sendStep3, sendStep4)
+            sendSteps(sendStep3, sendStep4)
 
             assertEquals(listOf<TransitionStep>(), currentStatesMapped)
         }
@@ -1134,6 +1134,63 @@
             )
         }
 
+    @Test
+    @EnableSceneContainer
+    fun transition_filter_on_belongsToInstantReversedTransition_out_of_lockscreen_scene() =
+        testScope.runTest {
+            val currentStatesMapped by
+                collectValues(underTest.transition(Edge.create(LOCKSCREEN, Scenes.Gone)))
+
+            kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
+            val sendStep1 = TransitionStep(UNDEFINED, LOCKSCREEN, 0f, STARTED)
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+            val sendStep2 = TransitionStep(UNDEFINED, LOCKSCREEN, 0.6f, CANCELED)
+            sendSteps(sendStep1, sendStep2)
+            val sendStep3 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            val sendStep4 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+            sendSteps(sendStep3, sendStep4)
+
+            assertEquals(listOf(sendStep3, sendStep4), currentStatesMapped)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun transition_filter_on_belongsToInstantReversedTransition_into_lockscreen_scene() =
+        testScope.runTest {
+            val currentStatesMapped by
+                collectValues(underTest.transition(Edge.create(Scenes.Gone, LOCKSCREEN)))
+
+            kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
+            val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+            val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 0.6f, CANCELED)
+            sendSteps(sendStep1, sendStep2)
+            val sendStep3 = TransitionStep(UNDEFINED, LOCKSCREEN, 0f, STARTED)
+            val sendStep4 = TransitionStep(UNDEFINED, LOCKSCREEN, 1f, FINISHED)
+            sendSteps(sendStep3, sendStep4)
+
+            assertEquals(listOf(sendStep3, sendStep4), currentStatesMapped)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun transition_filter_on_belongsToInstantReversedTransition_out_of_ls_with_wildcard() =
+        testScope.runTest {
+            val currentStatesMapped by
+                collectValues(underTest.transition(Edge.create(to = LOCKSCREEN)))
+
+            kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
+            val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+            val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 0.6f, CANCELED)
+            sendSteps(sendStep1, sendStep2)
+            val sendStep3 = TransitionStep(UNDEFINED, LOCKSCREEN, 0f, STARTED)
+            val sendStep4 = TransitionStep(UNDEFINED, LOCKSCREEN, 1f, FINISHED)
+            sendSteps(sendStep3, sendStep4)
+
+            assertEquals(listOf(sendStep3, sendStep4), currentStatesMapped)
+        }
+
     private suspend fun sendSteps(vararg steps: TransitionStep) {
         steps.forEach {
             repository.sendTransitionStep(it)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 09a6c2c..1899b7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -44,6 +44,7 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -384,6 +385,7 @@
             return mSpec;
         }
 
+        @NonNull
         @Override
         public State getState() {
             return mState;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 7a99aef..3f4a134 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -31,8 +31,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.settings.GlobalSettings
@@ -55,33 +57,22 @@
 @SmallTest
 class AirplaneModeTileTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var mHost: QSHost
-    @Mock
-    private lateinit var mMetricsLogger: MetricsLogger
-    @Mock
-    private lateinit var mStatusBarStateController: StatusBarStateController
-    @Mock
-    private lateinit var mActivityStarter: ActivityStarter
-    @Mock
-    private lateinit var mQsLogger: QSLogger
-    @Mock
-    private lateinit var mBroadcastDispatcher: BroadcastDispatcher
-    @Mock
-    private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
-    @Mock
-    private lateinit var mConnectivityManager: ConnectivityManager
-    @Mock
-    private lateinit var mGlobalSettings: GlobalSettings
-    @Mock
-    private lateinit var mUserTracker: UserTracker
-    @Mock
-    private lateinit var mUiEventLogger: QsEventLogger
+    @Mock private lateinit var mHost: QSHost
+    @Mock private lateinit var mMetricsLogger: MetricsLogger
+    @Mock private lateinit var mStatusBarStateController: StatusBarStateController
+    @Mock private lateinit var mActivityStarter: ActivityStarter
+    @Mock private lateinit var mQsLogger: QSLogger
+    @Mock private lateinit var mBroadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
+    @Mock private lateinit var mConnectivityManager: ConnectivityManager
+    @Mock private lateinit var mGlobalSettings: GlobalSettings
+    @Mock private lateinit var mUserTracker: UserTracker
+    @Mock private lateinit var mUiEventLogger: QsEventLogger
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: AirplaneModeTile
 
-    @Mock
-    private lateinit var mClickJob: Job
+    @Mock private lateinit var mClickJob: Job
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -89,20 +80,22 @@
         Mockito.`when`(mHost.context).thenReturn(mContext)
         Mockito.`when`(mHost.userContext).thenReturn(mContext)
         Mockito.`when`(mLazyConnectivityManager.get()).thenReturn(mConnectivityManager)
-        mTile = AirplaneModeTile(
-            mHost,
-            mUiEventLogger,
-            mTestableLooper.looper,
-            Handler(mTestableLooper.looper),
-            FalsingManagerFake(),
-            mMetricsLogger,
-            mStatusBarStateController,
-            mActivityStarter,
-            mQsLogger,
-            mBroadcastDispatcher,
-            mLazyConnectivityManager,
-            mGlobalSettings,
-            mUserTracker)
+        mTile =
+            AirplaneModeTile(
+                mHost,
+                mUiEventLogger,
+                mTestableLooper.looper,
+                Handler(mTestableLooper.looper),
+                FalsingManagerFake(),
+                mMetricsLogger,
+                mStatusBarStateController,
+                mActivityStarter,
+                mQsLogger,
+                mBroadcastDispatcher,
+                mLazyConnectivityManager,
+                mGlobalSettings,
+                mUserTracker,
+            )
     }
 
     @After
@@ -117,8 +110,7 @@
 
         mTile.handleUpdateState(state, 0)
 
-        assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_off))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_airplane_icon_off))
     }
 
     @Test
@@ -127,8 +119,7 @@
 
         mTile.handleUpdateState(state, 1)
 
-        assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_airplane_icon_on))
     }
 
     @Test
@@ -150,4 +141,12 @@
 
         verify(mConnectivityManager, times(0)).setAirplaneMode(any())
     }
+
+    private fun createExpectedIcon(resId: Int): QSTile.Icon {
+        return if (isEnabled) {
+            DrawableIconWithRes(mContext.getDrawable(resId), resId)
+        } else {
+            QSTileImpl.ResourceIcon.get(resId)
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index d6be314..ae00349 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -31,8 +31,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.util.settings.FakeSettings
@@ -94,7 +96,7 @@
                 activityStarter,
                 qsLogger,
                 batteryController,
-                secureSettings
+                secureSettings,
             )
 
         tile.initialize()
@@ -150,8 +152,7 @@
 
         tile.handleUpdateState(state, /* arg= */ null)
 
-        assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_off))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_battery_saver_icon_off))
     }
 
     @Test
@@ -161,7 +162,14 @@
 
         tile.handleUpdateState(state, /* arg= */ null)
 
-        assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_on))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_battery_saver_icon_on))
+    }
+
+    private fun createExpectedIcon(resId: Int): QSTile.Icon {
+        return if (isEnabled) {
+            DrawableIconWithRes(mContext.getDrawable(resId), resId)
+        } else {
+            QSTileImpl.ResourceIcon.get(resId)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 093cdf2..31519c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -23,7 +23,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
@@ -31,8 +30,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLoggerFake
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.google.common.truth.Truth.assertThat
@@ -41,8 +43,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -54,24 +56,15 @@
         const val CAMERA_TOGGLE_DISABLED: Boolean = true
     }
 
-    @Mock
-    private lateinit var host: QSHost
-    @Mock
-    private lateinit var metricsLogger: MetricsLogger
-    @Mock
-    private lateinit var statusBarStateController: StatusBarStateController
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
-    @Mock
-    private lateinit var qsLogger: QSLogger
-    @Mock
-    private lateinit var privacyController: IndividualSensorPrivacyController
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var uiEventLogger: QsEventLoggerFake
-    @Mock
-    private lateinit var safetyCenterManager: SafetyCenterManager
+    @Mock private lateinit var host: QSHost
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var qsLogger: QSLogger
+    @Mock private lateinit var privacyController: IndividualSensorPrivacyController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var uiEventLogger: QsEventLoggerFake
+    @Mock private lateinit var safetyCenterManager: SafetyCenterManager
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: CameraToggleTile
@@ -82,7 +75,8 @@
         testableLooper = TestableLooper.get(this)
         whenever(host.context).thenReturn(mContext)
 
-        tile = CameraToggleTile(
+        tile =
+            CameraToggleTile(
                 host,
                 uiEventLogger,
                 testableLooper.looper,
@@ -94,7 +88,8 @@
                 qsLogger,
                 privacyController,
                 keyguardStateController,
-                safetyCenterManager)
+                safetyCenterManager,
+            )
     }
 
     @After
@@ -109,8 +104,7 @@
 
         tile.handleUpdateState(state, CAMERA_TOGGLE_ENABLED)
 
-        assertThat(state.icon)
-                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_camera_access_icon_on))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_camera_access_icon_on))
     }
 
     @Test
@@ -119,14 +113,14 @@
 
         tile.handleUpdateState(state, CAMERA_TOGGLE_DISABLED)
 
-        assertThat(state.icon)
-                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_camera_access_icon_off))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_camera_access_icon_off))
     }
 
     @Test
     fun testLongClickIntent_safetyCenterEnabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
-        val cameraTile = CameraToggleTile(
+        val cameraTile =
+            CameraToggleTile(
                 host,
                 uiEventLogger,
                 testableLooper.looper,
@@ -138,7 +132,8 @@
                 qsLogger,
                 privacyController,
                 keyguardStateController,
-                safetyCenterManager)
+                safetyCenterManager,
+            )
         assertThat(cameraTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
         cameraTile.destroy()
         testableLooper.processAllMessages()
@@ -147,7 +142,8 @@
     @Test
     fun testLongClickIntent_safetyCenterDisabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
-        val cameraTile = CameraToggleTile(
+        val cameraTile =
+            CameraToggleTile(
                 host,
                 uiEventLogger,
                 testableLooper.looper,
@@ -159,9 +155,18 @@
                 qsLogger,
                 privacyController,
                 keyguardStateController,
-                safetyCenterManager)
+                safetyCenterManager,
+            )
         assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
         cameraTile.destroy()
         testableLooper.processAllMessages()
     }
+
+    private fun createExpectedIcon(resId: Int): QSTile.Icon {
+        return if (isEnabled) {
+            DrawableIconWithRes(mContext.getDrawable(resId), resId)
+        } else {
+            QSTileImpl.ResourceIcon.get(resId)
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
index 1343527..2c796a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.res.R;
@@ -133,7 +134,7 @@
         mTile.handleUpdateState(state, COLOR_INVERSION_DISABLED);
 
         assertThat(state.icon)
-                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_off));
+                .isEqualTo(createExpectedIcon(R.drawable.qs_invert_colors_icon_off));
     }
 
     @Test
@@ -143,6 +144,14 @@
         mTile.handleUpdateState(state, COLOR_INVERSION_ENABLED);
 
         assertThat(state.icon)
-                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_on));
+                .isEqualTo(createExpectedIcon(R.drawable.qs_invert_colors_icon_on));
+    }
+
+    private QSTile.Icon createExpectedIcon(int resId) {
+        if (QsInCompose.isEnabled()) {
+            return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+        } else {
+            return QSTileImpl.ResourceIcon.get(resId);
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
index 73ae4ee..23be9da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
@@ -29,8 +29,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.DataSaverController
@@ -84,7 +86,7 @@
                 mQsLogger,
                 dataSaverController,
                 mDialogTransitionAnimator,
-                systemUIDialogFactory
+                systemUIDialogFactory,
             )
     }
 
@@ -100,8 +102,7 @@
 
         tile.handleUpdateState(state, true)
 
-        assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_on))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_data_saver_icon_on))
     }
 
     @Test
@@ -110,7 +111,14 @@
 
         tile.handleUpdateState(state, false)
 
-        assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_off))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_data_saver_icon_off))
+    }
+
+    private fun createExpectedIcon(resId: Int): QSTile.Icon {
+        return if (isEnabled) {
+            DrawableIconWithRes(mContext.getDrawable(resId), resId)
+        } else {
+            QSTileImpl.ResourceIcon.get(resId)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
index f90463e..3cb9091 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
@@ -6,7 +6,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
@@ -14,8 +13,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.FlashlightController
 import com.google.common.truth.Truth
 import org.junit.After
@@ -69,7 +71,7 @@
                 statusBarStateController,
                 activityStarter,
                 qsLogger,
-                flashlightController
+                flashlightController,
             )
     }
 
@@ -87,8 +89,7 @@
 
         tile.handleUpdateState(state, /* arg= */ null)
 
-        Truth.assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_on))
+        Truth.assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_flashlight_icon_on))
     }
 
     @Test
@@ -100,7 +101,7 @@
         tile.handleUpdateState(state, /* arg= */ null)
 
         Truth.assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_off))
+            .isEqualTo(createExpectedIcon(R.drawable.qs_flashlight_icon_off))
     }
 
     @Test
@@ -111,6 +112,14 @@
         tile.handleUpdateState(state, /* arg= */ null)
 
         Truth.assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_off))
+            .isEqualTo(createExpectedIcon(R.drawable.qs_flashlight_icon_off))
+    }
+
+    private fun createExpectedIcon(resId: Int): QSTile.Icon {
+        return if (isEnabled) {
+            DrawableIconWithRes(mContext.getDrawable(resId), resId)
+        } else {
+            QSTileImpl.ResourceIcon.get(resId)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index b5ec0a0..b5a64b3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -35,9 +35,11 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.InternetDialogManager;
@@ -172,7 +174,7 @@
         mTile.mSignalCallback.setIsAirplaneMode(state);
         mTestableLooper.processAllMessages();
         assertThat(mTile.getState().icon).isEqualTo(
-                QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable));
+                createExpectedIcon(R.drawable.ic_qs_no_internet_unavailable));
     }
 
     @Test
@@ -194,4 +196,12 @@
 
         verify(mWifiStateWorker, times(1)).setWifiEnabled(eq(true));
     }
+
+    private QSTile.Icon createExpectedIcon(int resId) {
+        if (QsInCompose.isEnabled()) {
+            return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+        } else {
+            return QSTileImpl.ResourceIcon.get(resId);
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index 0a1455f..4be1899 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
@@ -30,9 +29,12 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.LocationController
 import com.android.systemui.util.mockito.argumentCaptor
@@ -52,27 +54,17 @@
 @SmallTest
 class LocationTileTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var mockContext: Context
-    @Mock
-    private lateinit var qsLogger: QSLogger
-    @Mock
-    private lateinit var qsHost: QSHost
-    @Mock
-    private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var qsLogger: QSLogger
+    @Mock private lateinit var qsHost: QSHost
+    @Mock private lateinit var metricsLogger: MetricsLogger
     private val falsingManager = FalsingManagerFake()
-    @Mock
-    private lateinit var statusBarStateController: StatusBarStateController
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
-    @Mock
-    private lateinit var locationController: LocationController
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var panelInteractor: PanelInteractor
-    @Mock
-    private lateinit var uiEventLogger: QsEventLogger
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var locationController: LocationController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var panelInteractor: PanelInteractor
+    @Mock private lateinit var uiEventLogger: QsEventLogger
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: LocationTile
@@ -83,20 +75,21 @@
         testableLooper = TestableLooper.get(this)
         `when`(qsHost.context).thenReturn(mockContext)
 
-        tile = LocationTile(
-            qsHost,
-            uiEventLogger,
-            testableLooper.looper,
-            Handler(testableLooper.looper),
-            falsingManager,
-            metricsLogger,
-            statusBarStateController,
-            activityStarter,
-            qsLogger,
-            locationController,
-            keyguardStateController,
-            panelInteractor,
-        )
+        tile =
+            LocationTile(
+                qsHost,
+                uiEventLogger,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                falsingManager,
+                metricsLogger,
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                locationController,
+                keyguardStateController,
+                panelInteractor,
+            )
     }
 
     @After
@@ -112,8 +105,7 @@
 
         tile.handleUpdateState(state, /* arg= */ null)
 
-        assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_off))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_location_icon_off))
     }
 
     @Test
@@ -123,8 +115,7 @@
 
         tile.handleUpdateState(state, /* arg= */ null)
 
-        assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_on))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_location_icon_on))
     }
 
     @Test
@@ -140,4 +131,12 @@
 
         verify(panelInteractor).openPanels()
     }
+
+    private fun createExpectedIcon(resId: Int): QSTile.Icon {
+        return if (isEnabled) {
+            DrawableIconWithRes(mContext.getDrawable(resId), resId)
+        } else {
+            QSTileImpl.ResourceIcon.get(resId)
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index dbdf3a4..afe9713 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -23,7 +23,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
@@ -31,8 +30,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.google.common.truth.Truth.assertThat
@@ -41,8 +43,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -54,36 +56,27 @@
         const val MICROPHONE_TOGGLE_DISABLED: Boolean = true
     }
 
-    @Mock
-    private lateinit var host: QSHost
-    @Mock
-    private lateinit var metricsLogger: MetricsLogger
-    @Mock
-    private lateinit var statusBarStateController: StatusBarStateController
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
-    @Mock
-    private lateinit var qsLogger: QSLogger
-    @Mock
-    private lateinit var privacyController: IndividualSensorPrivacyController
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var uiEventLogger: QsEventLogger
-    @Mock
-    private lateinit var safetyCenterManager: SafetyCenterManager
+    @Mock private lateinit var host: QSHost
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var qsLogger: QSLogger
+    @Mock private lateinit var privacyController: IndividualSensorPrivacyController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var uiEventLogger: QsEventLogger
+    @Mock private lateinit var safetyCenterManager: SafetyCenterManager
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: MicrophoneToggleTile
 
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         whenever(host.context).thenReturn(mContext)
 
-        tile = MicrophoneToggleTile(
+        tile =
+            MicrophoneToggleTile(
                 host,
                 uiEventLogger,
                 testableLooper.looper,
@@ -95,7 +88,8 @@
                 qsLogger,
                 privacyController,
                 keyguardStateController,
-                safetyCenterManager)
+                safetyCenterManager,
+            )
     }
 
     @After
@@ -110,7 +104,7 @@
 
         tile.handleUpdateState(state, MICROPHONE_TOGGLE_ENABLED)
 
-        assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_mic_access_on))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_mic_access_on))
     }
 
     @Test
@@ -119,13 +113,14 @@
 
         tile.handleUpdateState(state, MICROPHONE_TOGGLE_DISABLED)
 
-        assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_mic_access_off))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_mic_access_off))
     }
 
     @Test
     fun testLongClickIntent_safetyCenterEnabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
-        val micTile = MicrophoneToggleTile(
+        val micTile =
+            MicrophoneToggleTile(
                 host,
                 uiEventLogger,
                 testableLooper.looper,
@@ -137,7 +132,8 @@
                 qsLogger,
                 privacyController,
                 keyguardStateController,
-                safetyCenterManager)
+                safetyCenterManager,
+            )
         assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
         micTile.destroy()
         testableLooper.processAllMessages()
@@ -146,7 +142,8 @@
     @Test
     fun testLongClickIntent_safetyCenterDisabled() {
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
-        val micTile = MicrophoneToggleTile(
+        val micTile =
+            MicrophoneToggleTile(
                 host,
                 uiEventLogger,
                 testableLooper.looper,
@@ -158,9 +155,18 @@
                 qsLogger,
                 privacyController,
                 keyguardStateController,
-                safetyCenterManager)
+                safetyCenterManager,
+            )
         assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
         micTile.destroy()
         testableLooper.processAllMessages()
     }
+
+    private fun createExpectedIcon(resId: Int): QSTile.Icon {
+        return if (isEnabled) {
+            DrawableIconWithRes(mContext.getDrawable(resId), resId)
+        } else {
+            QSTileImpl.ResourceIcon.get(resId)
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 848c8db..9173ac9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -94,11 +94,7 @@
     private val zenModeRepository = kosmos.zenModeRepository
     private val tileDataInteractor =
         ModesTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher)
-    private val mapper =
-        ModesTileMapper(
-            context.resources,
-            context.theme,
-        )
+    private val mapper = ModesTileMapper(context.resources, context.theme)
 
     private lateinit var userActionInteractor: ModesTileUserActionInteractor
     private lateinit var secureSettings: SecureSettings
@@ -127,10 +123,7 @@
             )
 
         userActionInteractor =
-            ModesTileUserActionInteractor(
-                inputHandler,
-                dialogDelegate,
-            )
+            ModesTileUserActionInteractor(inputHandler, dialogDelegate, kosmos.zenModeInteractor)
 
         underTest =
             ModesTile(
@@ -185,7 +178,7 @@
                 ModesTileModel(
                     isActivated = true,
                     activeModes = listOf("One", "Two"),
-                    icon = TestStubDrawable().asIcon()
+                    icon = TestStubDrawable().asIcon(),
                 )
 
             underTest.handleUpdateState(tileState, model)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
index f1c5895..69dab39 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
@@ -23,7 +23,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dagger.NightDisplayListenerModule
@@ -32,8 +31,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.LocationController
 import com.google.common.truth.Truth
 import org.junit.After
@@ -42,8 +44,8 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -72,8 +74,6 @@
     private lateinit var mTestableLooper: TestableLooper
     private lateinit var mTile: NightDisplayTile
 
-
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -97,7 +97,7 @@
                 mQsLogger,
                 mLocationController,
                 mColorDisplayManager,
-                mNightDisplayListenerBuilder
+                mNightDisplayListenerBuilder,
             )
     }
 
@@ -115,7 +115,7 @@
         mTile.handleUpdateState(state, /* arg= */ null)
 
         Truth.assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_off))
+            .isEqualTo(createExpectedIcon(R.drawable.qs_nightlight_icon_off))
     }
 
     @Test
@@ -125,7 +125,14 @@
 
         mTile.handleUpdateState(state, /* arg= */ null)
 
-        Truth.assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_on))
+        Truth.assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_nightlight_icon_on))
+    }
+
+    private fun createExpectedIcon(resId: Int): QSTile.Icon {
+        return if (isEnabled) {
+            DrawableIconWithRes(mContext.getDrawable(resId), resId)
+        } else {
+            QSTileImpl.ResourceIcon.get(resId)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index f8f82f2..682daea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.res.R;
@@ -112,7 +113,7 @@
 
         assertEquals(state.label, mContext.getString(R.string.qr_code_scanner_title));
         assertEquals(state.contentDescription, mContext.getString(R.string.qr_code_scanner_title));
-        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.ic_qr_code_scanner));
+        assertEquals(state.icon, createExpectedIcon(R.drawable.ic_qr_code_scanner));
     }
 
     @Test
@@ -133,4 +134,12 @@
         assertEquals(state.state, Tile.STATE_INACTIVE);
         assertNull(state.secondaryLabel);
     }
+
+    private QSTile.Icon createExpectedIcon(int resId) {
+        if (QsInCompose.isEnabled()) {
+            return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+        } else {
+            return QSTileImpl.ResourceIcon.get(resId);
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 4068d9f..3395167 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -68,6 +68,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.res.R;
@@ -294,7 +295,7 @@
     public void testHandleUpdateState_updateLabelAndIcon_noIconFromApi() {
         when(mQuickAccessWalletClient.getTileIcon()).thenReturn(null);
         QSTile.State state = new QSTile.State();
-        QSTile.Icon icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_wallet_lockscreen);
+        QSTile.Icon icon = createExpectedIcon(R.drawable.ic_wallet_lockscreen);
 
         mTile.handleUpdateState(state, null);
 
@@ -574,5 +575,13 @@
                 CARD_ID, INVALID_CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build();
     }
 
+    private QSTile.Icon createExpectedIcon(int resId) {
+        if (QsInCompose.isEnabled()) {
+            return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+        } else {
+            return QSTileImpl.ResourceIcon.get(resId);
+        }
+    }
+
 
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 1aff45b..2345128 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -46,6 +46,7 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.ReduceBrightColorsController;
+import com.android.systemui.qs.flags.QsInCompose;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.res.R.drawable;
@@ -234,7 +235,7 @@
 
         mTile.handleUpdateState(state, /* arg= */ null);
 
-        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_on));
+        assertEquals(state.icon, createExpectedIcon(drawable.qs_extra_dim_icon_on));
     }
 
     @Test
@@ -245,7 +246,15 @@
 
         mTile.handleUpdateState(state, /* arg= */ null);
 
-        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_off));
+        assertEquals(state.icon, createExpectedIcon(drawable.qs_extra_dim_icon_off));
+    }
+
+    private QSTile.Icon createExpectedIcon(int resId) {
+        if (QsInCompose.isEnabled()) {
+            return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+        } else {
+            return QSTileImpl.ResourceIcon.get(resId);
+        }
     }
 
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 4193063..7fb0eab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -41,6 +41,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.res.R;
@@ -247,7 +248,7 @@
 
         mLockTile.handleUpdateState(state, /* arg= */ null);
 
-        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_auto_rotate_icon_off));
+        assertEquals(state.icon, createExpectedIcon(R.drawable.qs_auto_rotate_icon_off));
     }
 
     @Test
@@ -257,7 +258,7 @@
 
         mLockTile.handleUpdateState(state, /* arg= */ null);
 
-        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_auto_rotate_icon_on));
+        assertEquals(state.icon, createExpectedIcon(R.drawable.qs_auto_rotate_icon_on));
     }
 
 
@@ -281,4 +282,12 @@
     private void disableCameraBasedRotation() {
         when(mRotationPolicyWrapper.isCameraRotationEnabled()).thenReturn(false);
     }
+
+    private QSTile.Icon createExpectedIcon(int resId) {
+        if (QsInCompose.isEnabled()) {
+            return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+        } else {
+            return QSTileImpl.ResourceIcon.get(resId);
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 6ebe830..a7c7a78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -268,7 +269,7 @@
 
         mTile.handleUpdateState(state, /* arg= */ null);
 
-        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_on));
+        assertEquals(state.icon, createExpectedIcon(R.drawable.qs_screen_record_icon_on));
     }
 
     @Test
@@ -279,7 +280,7 @@
 
         mTile.handleUpdateState(state, /* arg= */ null);
 
-        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_on));
+        assertEquals(state.icon, createExpectedIcon(R.drawable.qs_screen_record_icon_on));
     }
 
     @Test
@@ -290,7 +291,7 @@
 
         mTile.handleUpdateState(state, /* arg= */ null);
 
-        assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off));
+        assertEquals(state.icon, createExpectedIcon(R.drawable.qs_screen_record_icon_off));
     }
 
     @Test
@@ -316,4 +317,12 @@
                 .notifyPermissionRequestDisplayed(mContext.getUserId());
     }
 
+    private QSTile.Icon createExpectedIcon(int resId) {
+        if (QsInCompose.isEnabled()) {
+            return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+        } else {
+            return QSTileImpl.ResourceIcon.get(resId);
+        }
+    }
+
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index 8324a73..773e225 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
@@ -25,7 +25,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
@@ -33,8 +32,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.LocationController
@@ -95,7 +97,7 @@
                 qsLogger,
                 configurationController,
                 batteryController,
-                locationController
+                locationController,
             )
     }
 
@@ -112,8 +114,7 @@
 
         tile.handleUpdateState(state, /* arg= */ null)
 
-        assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_on))
+        assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_light_dark_theme_icon_on))
     }
 
     @Test
@@ -124,7 +125,7 @@
         tile.handleUpdateState(state, /* arg= */ null)
 
         assertThat(state.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_off))
+            .isEqualTo(createExpectedIcon(R.drawable.qs_light_dark_theme_icon_off))
     }
 
     private fun setNightModeOn() {
@@ -136,4 +137,12 @@
         `when`(uiModeManager.nightMode).thenReturn(UiModeManager.MODE_NIGHT_NO)
         configuration.uiMode = Configuration.UI_MODE_NIGHT_NO
     }
+
+    private fun createExpectedIcon(resId: Int): QSTile.Icon {
+        return if (isEnabled) {
+            DrawableIconWithRes(mContext.getDrawable(resId), resId)
+        } else {
+            QSTileImpl.ResourceIcon.get(resId)
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index cd58127..88b0046 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.qs.tiles.impl.modes.domain.interactor
 
 import android.graphics.drawable.TestStubDrawable
@@ -21,16 +23,23 @@
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
 import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -43,17 +52,17 @@
 @EnableFlags(android.app.Flags.FLAG_MODES_UI)
 class ModesTileUserActionInteractorTest : SysuiTestCase() {
     private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private val inputHandler = kosmos.qsTileIntentUserInputHandler
     private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+    private val zenModeRepository = kosmos.zenModeRepository
+    private val zenModeInteractor = kosmos.zenModeInteractor
 
     private val underTest =
-        ModesTileUserActionInteractor(
-            inputHandler,
-            mockDialogDelegate,
-        )
+        ModesTileUserActionInteractor(inputHandler, mockDialogDelegate, zenModeInteractor)
 
     @Test
-    fun handleClick_active() = runTest {
+    fun handleClick_active_showsDialog() = runTest {
         val expandable = mock<Expandable>()
         underTest.handleInput(
             QSTileInputTestKtx.click(data = modelOf(true, listOf("DND")), expandable = expandable)
@@ -63,7 +72,7 @@
     }
 
     @Test
-    fun handleClick_inactive() = runTest {
+    fun handleClick_inactive_showsDialog() = runTest {
         val expandable = mock<Expandable>()
         underTest.handleInput(
             QSTileInputTestKtx.click(data = modelOf(false, emptyList()), expandable = expandable)
@@ -73,7 +82,63 @@
     }
 
     @Test
-    fun handleLongClick_active() = runTest {
+    @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+    fun handleToggleClick_multipleModesActive_deactivatesAll() =
+        testScope.runTest {
+            val activeModes by collectLastValue(zenModeInteractor.activeModes)
+
+            zenModeRepository.addModes(
+                listOf(
+                    TestModeBuilder.MANUAL_DND_ACTIVE,
+                    TestModeBuilder().setName("Mode 1").setActive(true).build(),
+                    TestModeBuilder().setName("Mode 2").setActive(true).build(),
+                )
+            )
+            assertThat(activeModes?.modeNames?.count()).isEqualTo(3)
+
+            underTest.handleInput(
+                QSTileInputTestKtx.toggleClick(
+                    data = modelOf(true, listOf("DND", "Mode 1", "Mode 2"))
+                )
+            )
+
+            assertThat(activeModes?.isAnyActive()).isFalse()
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+    fun handleToggleClick_dndActive_deactivatesDnd() =
+        testScope.runTest {
+            val dndMode by collectLastValue(zenModeInteractor.dndMode)
+
+            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+            assertThat(dndMode?.isActive).isTrue()
+
+            underTest.handleInput(
+                QSTileInputTestKtx.toggleClick(data = modelOf(true, listOf("DND")))
+            )
+
+            assertThat(dndMode?.isActive).isFalse()
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+    fun handleToggleClick_dndInactive_activatesDnd() =
+        testScope.runTest {
+            val dndMode by collectLastValue(zenModeInteractor.dndMode)
+
+            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+            assertThat(dndMode?.isActive).isFalse()
+
+            underTest.handleInput(
+                QSTileInputTestKtx.toggleClick(data = modelOf(false, emptyList()))
+            )
+
+            assertThat(dndMode?.isActive).isTrue()
+        }
+
+    @Test
+    fun handleLongClick_active_opensSettings() = runTest {
         underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(true, listOf("DND"))))
 
         QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
@@ -82,7 +147,7 @@
     }
 
     @Test
-    fun handleLongClick_inactive() = runTest {
+    fun handleLongClick_inactive_opensSettings() = runTest {
         underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(false, emptyList())))
 
         QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
index 1b3f29a..dd1b369 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.core
 
+import android.internal.statusbar.FakeStatusBarService.Companion.SECONDARY_DISPLAY_ID
 import android.internal.statusbar.fakeStatusBarService
 import android.platform.test.annotations.EnableFlags
 import android.view.WindowInsets
@@ -53,7 +54,7 @@
     }
 
     @Test
-    fun start_barResultHasTransientStatusBar_transientStateIsTrue() {
+    fun start_defaultDisplay_barResultHasTransientStatusBar_transientStateIsTrue() {
         fakeStatusBarService.transientBarTypes = WindowInsets.Type.statusBars()
 
         initializer.start()
@@ -62,7 +63,7 @@
     }
 
     @Test
-    fun start_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
+    fun start_defaultDisplay_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
         fakeStatusBarService.transientBarTypes = WindowInsets.Type.navigationBars()
 
         initializer.start()
@@ -71,6 +72,32 @@
     }
 
     @Test
+    fun start_secondaryDisplay_barResultHasTransientStatusBar_transientStateIsTrue() {
+        fakeStatusBarService.transientBarTypesSecondaryDisplay = WindowInsets.Type.statusBars()
+        fakeStatusBarService.transientBarTypes = WindowInsets.Type.navigationBars()
+
+        initializer.start()
+
+        assertThat(statusBarModeRepository.forDisplay(SECONDARY_DISPLAY_ID).isTransientShown.value)
+            .isTrue()
+        // Default display should be unaffected
+        assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isFalse()
+    }
+
+    @Test
+    fun start_secondaryDisplay_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
+        fakeStatusBarService.transientBarTypesSecondaryDisplay = WindowInsets.Type.navigationBars()
+        fakeStatusBarService.transientBarTypes = WindowInsets.Type.statusBars()
+
+        initializer.start()
+
+        assertThat(statusBarModeRepository.forDisplay(SECONDARY_DISPLAY_ID).isTransientShown.value)
+            .isFalse()
+        // Default display should be unaffected
+        assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isTrue()
+    }
+
+    @Test
     fun start_callsOnSystemBarAttributesChanged_basedOnRegisterBarResult() {
         initializer.start()
 
@@ -85,6 +112,17 @@
                 fakeStatusBarService.packageName,
                 fakeStatusBarService.letterboxDetails,
             )
+        verify(commandQueueCallbacks)
+            .onSystemBarAttributesChanged(
+                SECONDARY_DISPLAY_ID,
+                fakeStatusBarService.appearanceSecondaryDisplay,
+                fakeStatusBarService.appearanceRegionsSecondaryDisplay,
+                fakeStatusBarService.navbarColorManagedByImeSecondaryDisplay,
+                fakeStatusBarService.behaviorSecondaryDisplay,
+                fakeStatusBarService.requestedVisibleTypesSecondaryDisplay,
+                fakeStatusBarService.packageNameSecondaryDisplay,
+                fakeStatusBarService.letterboxDetailsSecondaryDisplay,
+            )
     }
 
     @Test
@@ -105,6 +143,14 @@
                 fakeStatusBarService.imeBackDisposition,
                 fakeStatusBarService.showImeSwitcher,
             )
+
+        verify(commandQueueCallbacks)
+            .setImeWindowStatus(
+                SECONDARY_DISPLAY_ID,
+                fakeStatusBarService.imeWindowVisSecondaryDisplay,
+                fakeStatusBarService.imeBackDispositionSecondaryDisplay,
+                fakeStatusBarService.showImeSwitcherSecondaryDisplay,
+            )
     }
 
     @Test
@@ -117,6 +163,11 @@
             .isEqualTo(fakeStatusBarService.disabledFlags1)
         assertThat(commandQueue.disableFlags2ForDisplay(context.displayId))
             .isEqualTo(fakeStatusBarService.disabledFlags2)
+
+        assertThat(commandQueue.disableFlags1ForDisplay(SECONDARY_DISPLAY_ID))
+            .isEqualTo(fakeStatusBarService.disabledFlags1SecondaryDisplay)
+        assertThat(commandQueue.disableFlags2ForDisplay(SECONDARY_DISPLAY_ID))
+            .isEqualTo(fakeStatusBarService.disabledFlags2SecondaryDisplay)
     }
 
     @Test
@@ -125,5 +176,7 @@
 
         assertThat(commandQueue.disableFlags1ForDisplay(context.displayId)).isNull()
         assertThat(commandQueue.disableFlags2ForDisplay(context.displayId)).isNull()
+        assertThat(commandQueue.disableFlags1ForDisplay(SECONDARY_DISPLAY_ID)).isNull()
+        assertThat(commandQueue.disableFlags2ForDisplay(SECONDARY_DISPLAY_ID)).isNull()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 4e33a59..3d6882c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -251,6 +251,22 @@
         }
 
     @Test
+    fun deactivateAllModes_updatesCorrectModes() =
+        testScope.runTest {
+            zenModeRepository.addModes(
+                listOf(
+                    TestModeBuilder.MANUAL_DND_ACTIVE,
+                    TestModeBuilder().setName("Inactive").setActive(false).build(),
+                    TestModeBuilder().setName("Active").setActive(true).build(),
+                )
+            )
+
+            underTest.deactivateAllModes()
+
+            assertThat(zenModeRepository.getModes().filter { it.isActive }).isEmpty()
+        }
+
+    @Test
     fun activeModes_computesMainActiveMode() =
         testScope.runTest {
             val activeModes by collectLastValue(underTest.activeModes)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index e3cbd66..322da32 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.plugins.qs;
 
-import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -22,6 +21,7 @@
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.InstanceId;
@@ -92,6 +92,7 @@
 
     CharSequence getTileLabel();
 
+    @NonNull
     State getState();
 
     default LogMaker populate(LogMaker logMaker) {
@@ -269,6 +270,7 @@
             return sb.append(']');
         }
 
+        @NonNull
         public State copy() {
             State state = new State();
             copyTo(state);
@@ -304,6 +306,7 @@
             return rt;
         }
 
+        @androidx.annotation.NonNull
         @Override
         public State copy() {
             AdapterState state = new AdapterState();
@@ -316,6 +319,7 @@
     class BooleanState extends AdapterState {
         public static final int VERSION = 1;
 
+        @androidx.annotation.NonNull
         @Override
         public State copy() {
             BooleanState state = new BooleanState();
diff --git a/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml b/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml
new file mode 100644
index 0000000..0640116
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M480,616 L240,376l56,-56 184,184 184,-184 56,56 -240,240Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml b/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml
new file mode 100644
index 0000000..65a3eef
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M480,432 L296,616l-56,-56 240,-240 240,240 -56,56 -184,-184Z" />
+</vector>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e417da4..658f2c2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1509,6 +1509,9 @@
     <!-- Content description for accessibility: Tapping this button will dismiss all gentle notifications [CHAR LIMIT=NONE] -->
     <string name="accessibility_notification_section_header_gentle_clear_all">Clear all silent notifications</string>
 
+    <!-- Content description for accessibility: Tapping this button will open notifications settings [CHAR LIMIT=NONE] -->
+    <string name="accessibility_notification_section_header_open_settings">Open notifications settings</string>
+
     <!-- The text to show in the notifications shade when dnd is suppressing notifications. [CHAR LIMIT=100] -->
     <string name="dnd_suppressing_shade_text">Notifications paused by Do Not Disturb</string>
 
@@ -1812,6 +1815,7 @@
 
     <string name="volume_ringer_change">Tap to change ringer mode</string>
     <string name="volume_ringer_mode">ringer mode</string>
+    <string name="volume_ringer_drawer_closed_content_description"><xliff:g id="volume ringer status" example="Ring">%1$s</xliff:g>, tap to change ringer mode </string>
 
     <!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
     <string name="volume_ringer_hint_mute">mute</string>
@@ -3949,6 +3953,8 @@
     <string name="tutorial_action_key_success_title">Well done!</string>
     <!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] -->
     <string name="tutorial_action_key_success_body">You completed the view all apps gesture</string>
+    <!-- Content description for the animation playing during the tutorial. The user can click the animation to pause and unpause playback. [CHAR LIMIT=NONE] -->
+    <string name="tutorial_animation_content_description">Tutorial animation, click to pause and resume play.</string>
 
     <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
     <string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index a46b236..9817322 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -46,6 +46,8 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.ConfigurationForwarder;
 import com.android.systemui.util.NotificationChannels;
+import com.android.wm.shell.dagger.HasWMComponent;
+import com.android.wm.shell.dagger.WMComponent;
 
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayDeque;
@@ -62,7 +64,7 @@
  * Application class for SystemUI.
  */
 public class SystemUIApplication extends Application implements
-        SystemUIAppComponentFactoryBase.ContextInitializer {
+        SystemUIAppComponentFactoryBase.ContextInitializer, HasWMComponent {
 
     public static final String TAG = "SystemUIService";
     private static final boolean DEBUG = false;
@@ -490,4 +492,10 @@
 
         n.addExtras(extras);
     }
+
+    @NonNull
+    @Override
+    public WMComponent getWMComponent() {
+        return mInitializer.getWMComponent();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 5c75a49..f530522 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -24,9 +24,9 @@
 
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
-import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.res.R;
 import com.android.systemui.util.InitializationChecker;
+import com.android.wm.shell.dagger.WMComponent;
 import com.android.wm.shell.dagger.WMShellConcurrencyModule;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 import com.android.wm.shell.shared.ShellTransitions;
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index cbdb882..5cf4b4f 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -242,6 +242,7 @@
                                 op.getUid(),
                                 op.getPackageName(),
                                 /* attributionTag= */ attributedOpEntry.getKey(),
+                                Context.DEVICE_ID_DEFAULT,
                                 /* active= */ true,
                                 // AppOpsManager doesn't have a way to fetch attribution flags or
                                 // chain ID given an op entry, so default them to none.
@@ -440,14 +441,14 @@
      * Required to override, delegate to other. Should not be called.
      */
     public void onOpActiveChanged(String op, int uid, String packageName, boolean active) {
-        onOpActiveChanged(op, uid, packageName, null, active,
+        onOpActiveChanged(op, uid, packageName, null, Context.DEVICE_ID_DEFAULT, active,
                 AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
     }
 
     // Get active app ops, and check if their attributions are trusted
     @Override
     public void onOpActiveChanged(String op, int uid, String packageName, String attributionTag,
-            boolean active, int attributionFlags, int attributionChainId) {
+            int virtualDeviceId, boolean active, int attributionFlags, int attributionChainId) {
         int code = AppOpsManager.strOpToOp(op);
         if (DEBUG) {
             Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s,%d,%d)", code, uid, packageName,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 9763295..8a5e011 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
+import kotlin.math.max
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -39,7 +40,6 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import kotlin.math.max
 
 /** Encapsulates business logic for interacting with the UDFPS overlay. */
 @SysUISingleton
@@ -55,10 +55,7 @@
     private fun calculateIconSize(): Int {
         val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
         if (pixelPitch <= 0) {
-            Log.e(
-                "UdfpsOverlayInteractor",
-                "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.",
-            )
+            Log.e(TAG, "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.")
         }
         return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
     }
@@ -84,13 +81,15 @@
     }
 
     /** Sets whether Udfps overlay should handle touches */
-    fun setHandleTouches(shouldHandle: Boolean = true) {
-        if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) {
+    fun setHandleTouches(shouldHandle: Boolean) {
+        if (authController.isUdfpsSupported) {
             fingerprintManager?.setIgnoreDisplayTouches(
                 requestId.value,
                 authController.udfpsProps!!.get(0).sensorId,
                 !shouldHandle,
             )
+        } else {
+            Log.d(TAG, "setIgnoreDisplayTouches not set, UDFPS not supported")
         }
         _shouldHandleTouches.value = shouldHandle
     }
@@ -123,12 +122,14 @@
 
     // Padding between the fingerprint icon and its bounding box in pixels.
     val iconPadding: Flow<Int> =
-        udfpsOverlayParams.map { params ->
-            val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
-            val nativePadding = (sensorWidth - iconSize) / 2
-            // padding can be negative when udfpsOverlayParams has not been initialized yet.
-            max(0, (nativePadding * params.scaleFactor).toInt())
-        }.distinctUntilChanged()
+        udfpsOverlayParams
+            .map { params ->
+                val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
+                val nativePadding = (sensorWidth - iconSize) / 2
+                // padding can be negative when udfpsOverlayParams has not been initialized yet.
+                max(0, (nativePadding * params.scaleFactor).toInt())
+            }
+            .distinctUntilChanged()
 
     companion object {
         private const val TAG = "UdfpsOverlayInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index e78ce3b..f804c2e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -24,6 +24,7 @@
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.util.InitializationChecker;
+import com.android.wm.shell.dagger.WMComponent;
 
 import dagger.BindsInstance;
 
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
index abd39cc..ad18817 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
@@ -36,6 +36,9 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.node.Ref
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.util.lerp
 import com.airbnb.lottie.LottieComposition
 import com.airbnb.lottie.compose.LottieAnimation
@@ -47,6 +50,7 @@
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.res.R
 
 @Composable
 fun TutorialAnimation(
@@ -104,11 +108,15 @@
             isPlaying = isPlaying,
             restartOnPlay = false,
         )
+    val animationDescription = stringResource(R.string.tutorial_animation_content_description)
     LottieAnimation(
         composition = composition,
         progress = { progress },
         dynamicProperties = animationProperties,
-        modifier = Modifier.fillMaxSize().clickable { isPlaying = !isPlaying },
+        modifier =
+            Modifier.fillMaxSize()
+                .clickable { isPlaying = !isPlaying }
+                .semantics { contentDescription = animationDescription },
     )
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index d1f9fa2..e8d3bfa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -27,7 +27,6 @@
 import android.service.notification.ZenModeConfig
 import android.util.Log
 import com.android.settingslib.notification.modes.EnableZenModeDialog
-import com.android.settingslib.notification.modes.ZenMode
 import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -99,15 +98,6 @@
     private var oldIsAvailable = false
     private var settingsValue: Int = 0
 
-    private val dndMode: StateFlow<ZenMode?> by lazy {
-        ModesUi.assertInNewMode()
-        interactor.dndMode.stateIn(
-            scope = backgroundScope,
-            started = SharingStarted.Eagerly,
-            initialValue = null,
-        )
-    }
-
     private val isAvailable: StateFlow<Boolean> by lazy {
         ModesUi.assertInNewMode()
         interactor.isZenAvailable.stateIn(
@@ -146,7 +136,7 @@
 
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
         if (ModesUi.isEnabled) {
-            combine(isAvailable, dndMode) { isAvailable, dndMode ->
+            combine(isAvailable, interactor.dndMode) { isAvailable, dndMode ->
                 if (!isAvailable) {
                     KeyguardQuickAffordanceConfig.LockScreenState.Hidden
                 } else if (dndMode?.isActive == true) {
@@ -222,7 +212,7 @@
             if (!isAvailable.value) {
                 KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
             } else {
-                val dnd = dndMode.value
+                val dnd = interactor.dndMode.value
                 if (dnd == null) {
                     Log.wtf(TAG, "Triggered DND but it's null!?")
                     return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index deef2a6..25e6f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -49,7 +49,6 @@
 import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.filter
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -226,8 +225,15 @@
 
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .filter { !it }
-                .sample(deviceEntryInteractor.isUnlocked, ::Pair)
+                .filterRelevantKeyguardStateAnd { !it }
+                .sample(
+                    if (SceneContainerFlag.isEnabled) {
+                        deviceEntryInteractor.isUnlocked
+                    } else {
+                        keyguardInteractor.isKeyguardDismissible
+                    },
+                    ::Pair,
+                )
                 .collect { (_, dismissable) ->
                     // TODO(b/349837588): Add check for -> OCCLUDED.
                     if (dismissable) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 7cd2744..f078fe2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -256,6 +256,16 @@
             val isTransitioningBetweenDesiredScenes =
                 sceneInteractor.transitionState.value.isTransitioning(fromScene, toScene)
 
+            // When in STL A -> B settles in A we can't do the same in KTF as KTF requires us to
+            // start B -> A to get back to A. [LockscreenSceneTransitionInteractor] will emit these
+            // steps but because STL is Idle(A) at this point (and never even started a B -> A in
+            // the first place) [isTransitioningBetweenDesiredScenes] will not be satisfied. We need
+            // this condition to not filter out the STARTED and FINISHED step of the "artificially"
+            // reversed B -> A transition.
+            val belongsToInstantReversedTransition =
+                sceneInteractor.transitionState.value.isIdle(toScene) &&
+                    sceneTransitionPair.value.previousValue.isTransitioning(toScene, fromScene)
+
             // We can't compare the terminal step with the current sceneTransition because
             // a) STL has no guarantee that it will settle in Idle() when finished/canceled
             // b) Comparing to Idle(toScene) would make any other FINISHED step settling in
@@ -267,7 +277,8 @@
 
             return@filter isTransitioningBetweenLockscreenStates ||
                 isTransitioningBetweenDesiredScenes ||
-                terminalStepBelongsToPreviousTransition
+                terminalStepBelongsToPreviousTransition ||
+                belongsToInstantReversedTransition
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 9df293b..3b99bb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -276,7 +276,11 @@
                     false
                 } else if (
                     currentState == KeyguardState.DREAMING &&
-                        deviceEntryInteractor.get().isUnlocked.value
+                        if (SceneContainerFlag.isEnabled) {
+                            deviceEntryInteractor.get().isUnlocked.value
+                        } else {
+                            keyguardInteractor.isKeyguardDismissible.value
+                        }
                 ) {
                     // Dreams dismiss keyguard and return to GONE if they can.
                     false
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index c798e5b..13b3311 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -126,7 +126,7 @@
     modifier: Modifier = Modifier,
     detailsViewModel: DetailsViewModel?,
 ) {
-    val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
+    val state: QSTile.State by tile.state.collectAsStateWithLifecycle(tile.currentState)
     val currentBounceableInfo by rememberUpdatedState(bounceableInfo)
     val resources = resources()
     val uiState = remember(state, resources) { state.toUiState(resources) }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
index 44dd801..9462321 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
@@ -24,6 +24,7 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.onStart
 
 @Immutable
@@ -37,6 +38,7 @@
                 awaitClose { tile.removeCallback(callback) }
             }
             .onStart { emit(tile.state) }
+            .filterNotNull()
             .distinctUntilChanged()
 
     val currentState: QSTile.State
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 464eeda..1d1e991 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -369,6 +369,7 @@
         mHandler.sendEmptyMessage(H.INITIALIZE);
     }
 
+    @androidx.annotation.NonNull
     public TState getState() {
         return mState;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 9c63456..0051bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -109,6 +109,11 @@
         userActionInteractor.handleClick(expandable)
     }
 
+    override fun handleSecondaryClick(expandable: Expandable?) = runBlocking {
+        val model = dataInteractor.getCurrentTileModel()
+        userActionInteractor.handleToggleClick(model)
+    }
+
     override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
 
     @VisibleForTesting
@@ -125,6 +130,7 @@
             secondaryLabel = tileState.secondaryLabel
             contentDescription = tileState.contentDescription
             expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
+            handlesSecondaryClick = true
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 244f024..bed8021 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -149,7 +149,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final TelephonyDisplayInfo DEFAULT_TELEPHONY_DISPLAY_INFO =
             new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
+                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false);
 
     static final int MAX_WIFI_ENTRY_COUNT = 3;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index eb8b23c..594394f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -18,13 +18,16 @@
 
 import android.content.Intent
 import android.provider.Settings
+import android.util.Log
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.flags.QSComposeFragment
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
 import javax.inject.Inject
 
@@ -35,16 +38,19 @@
     private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
     // TODO(b/353896370): The domain layer should not have to depend on the UI layer.
     private val dialogDelegate: ModesDialogDelegate,
+    private val zenModeInteractor: ZenModeInteractor,
 ) : QSTileUserActionInteractor<ModesTileModel> {
     val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
 
     override suspend fun handleInput(input: QSTileInput<ModesTileModel>) {
         with(input) {
             when (action) {
-                is QSTileUserAction.Click,
-                is QSTileUserAction.ToggleClick -> {
+                is QSTileUserAction.Click -> {
                     handleClick(action.expandable)
                 }
+                is QSTileUserAction.ToggleClick -> {
+                    handleToggleClick(input.data)
+                }
                 is QSTileUserAction.LongClick -> {
                     qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent)
                 }
@@ -56,4 +62,29 @@
         // Show a dialog with the list of modes to configure.
         dialogDelegate.showDialog(expandable)
     }
+
+    fun handleToggleClick(modesTileModel: ModesTileModel) {
+        if (QSComposeFragment.isUnexpectedlyInLegacyMode()) {
+            return
+        }
+
+        // If no modes are on, turn on DND since it's the highest-priority mode. Otherwise, turn
+        // them all off.
+        // We want this toggle to work as a shortcut to DND in most cases, but it should still
+        // correctly toggle the tile state to "off" as the user would expect when more modes are on.
+        if (modesTileModel.activeModes.isEmpty()) {
+            val dnd = zenModeInteractor.dndMode.value
+            if (dnd == null) {
+                Log.wtf(TAG, "Triggered DND but it's null!?")
+                return
+            }
+            zenModeInteractor.activateMode(dnd)
+        } else {
+            zenModeInteractor.deactivateAllModes()
+        }
+    }
+
+    companion object {
+        const val TAG = "ModesTileUserActionInteractor"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index bac048f..1507ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -45,7 +45,11 @@
             secondaryLabel = getModesStatus(data, resources)
             contentDescription = "$label. $secondaryLabel"
             supportedActions =
-                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                setOf(
+                    QSTileState.UserAction.CLICK,
+                    QSTileState.UserAction.LONG_CLICK,
+                    QSTileState.UserAction.TOGGLE_CLICK,
+                )
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             expandedAccessibilityClass = Button::class
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index f9a1ad5..9d902d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -207,8 +207,9 @@
         qsTileViewModel.destroy()
     }
 
-    override fun getState(): QSTile.State? =
+    override fun getState(): QSTile.State =
         qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) }
+            ?: QSTile.State()
 
     override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
index c5c705c..4ad222d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -162,7 +162,12 @@
             )
 
         if (cutout == null) {
-            screenshotStatic.setPadding(0, 0, 0, navBarInsets.bottom)
+            screenshotStatic.setPadding(
+                navBarInsets.left,
+                navBarInsets.top,
+                navBarInsets.right,
+                navBarInsets.bottom,
+            )
         } else {
             val waterfall = cutout.waterfallInsets
             if (inPortrait) {
@@ -179,9 +184,9 @@
                 )
             } else {
                 screenshotStatic.setPadding(
-                    max(cutout.safeInsetLeft, waterfall.left),
+                    max(cutout.safeInsetLeft, waterfall.left, navBarInsets.left),
                     waterfall.top,
-                    max(cutout.safeInsetRight, waterfall.right),
+                    max(cutout.safeInsetRight, waterfall.right, navBarInsets.right),
                     max(
                         navBarInsets.bottom + verticalPadding,
                         waterfall.bottom + verticalPadding,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index c37b01f..a9c2784 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -49,7 +49,7 @@
 ) : ConnectivityState() {
 
     @JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false)
+            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false)
     @JvmField var serviceState: ServiceState? = null
     @JvmField var signalStrength: SignalStrength? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
index 614f0f4..d24edda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.os.Binder
 import android.os.RemoteException
+import android.view.Display
 import android.view.WindowInsets
 import com.android.internal.statusbar.IStatusBarService
 import com.android.internal.statusbar.RegisterStatusBarResult
@@ -47,20 +48,32 @@
 
     override fun start() {
         StatusBarConnectedDisplays.assertInNewMode()
-        val result: RegisterStatusBarResult =
+        val resultPerDisplay: Map<String, RegisterStatusBarResult> =
             try {
-                barService.registerStatusBar(commandQueue)
+                barService.registerStatusBarForAllDisplays(commandQueue)
             } catch (ex: RemoteException) {
                 ex.rethrowFromSystemServer()
                 return
             }
 
-        createNavigationBar(result)
-
-        if ((result.mTransientBarTypes and WindowInsets.Type.statusBars()) != 0) {
-            statusBarModeRepository.defaultDisplay.showTransient()
+        resultPerDisplay[Display.DEFAULT_DISPLAY.toString()]?.let {
+            createNavigationBar(it)
+            // Set up the initial icon state
+            val numIcons: Int = it.mIcons.size
+            for (i in 0 until numIcons) {
+                commandQueue.setIcon(it.mIcons.keyAt(i), it.mIcons.valueAt(i))
+            }
         }
-        val displayId = context.display.displayId
+
+        for ((displayId, result) in resultPerDisplay.entries) {
+            initializeStatusBarForDisplay(displayId.toInt(), result)
+        }
+    }
+
+    private fun initializeStatusBarForDisplay(displayId: Int, result: RegisterStatusBarResult) {
+        if ((result.mTransientBarTypes and WindowInsets.Type.statusBars()) != 0) {
+            statusBarModeRepository.forDisplay(displayId).showTransient()
+        }
         val commandQueueCallbacks = commandQueueCallbacksLazy.get()
         commandQueueCallbacks.onSystemBarAttributesChanged(
             displayId,
@@ -81,12 +94,6 @@
             result.mShowImeSwitcher,
         )
 
-        // Set up the initial icon state
-        val numIcons: Int = result.mIcons.size
-        for (i in 0 until numIcons) {
-            commandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i))
-        }
-
         // set the initial view visibility
         val disabledFlags1 = result.mDisabledFlags1
         val disabledFlags2 = result.mDisabledFlags2
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
index 969ff1b..44075af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK;
+
 import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
@@ -28,6 +30,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.core.view.ViewCompat;
+
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 
@@ -69,6 +73,13 @@
             mLabelView.setText(mLabelTextId);
         }
         mLabelView.setAccessibilityHeading(true);
+        ViewCompat.replaceAccessibilityAction(
+                mLabelView,
+                ACTION_CLICK,
+                getResources().getString(
+                        R.string.accessibility_notification_section_header_open_settings),
+                null
+        );
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index 9839f9d..12ed647 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -40,12 +40,16 @@
 import java.time.Duration
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 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.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * An interactor that performs business logic related to the status and configuration of Zen Mode
@@ -58,6 +62,7 @@
     private val zenModeRepository: ZenModeRepository,
     private val notificationSettingsRepository: NotificationSettingsRepository,
     @Background private val bgDispatcher: CoroutineDispatcher,
+    @Background private val backgroundScope: CoroutineScope,
     private val iconLoader: ZenIconLoader,
     deviceProvisioningRepository: DeviceProvisioningRepository,
     userSetupRepository: UserSetupRepository,
@@ -101,13 +106,16 @@
     /**
      * Returns the special "manual DND" mode.
      *
-     * This is only meant as a temporary solution for "legacy" UI pieces that handle DND
-     * specifically; any new or migrated features should use modes more generally, through [modes]
-     * or [activeModes].
+     * This should only be used when there is a strong reason to handle DND specifically (such as
+     * legacy UI pieces that haven't been updated to use modes more generally, or if the user
+     * explicitly wants a shortcut to DND). Please prefer using [modes] or [activeModes] in all
+     * other scenarios.
      */
-    val dndMode: Flow<ZenMode?> by lazy {
+    val dndMode: StateFlow<ZenMode?> by lazy {
         ModesUi.assertInNewMode()
-        zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } }
+        zenModeRepository.modes
+            .map { modes -> modes.singleOrNull { it.isManualDnd } }
+            .stateIn(scope = backgroundScope, started = SharingStarted.Eagerly, initialValue = null)
     }
 
     /** Flow returning the currently active mode(s), if any. */
@@ -201,6 +209,14 @@
         zenModeRepository.deactivateMode(zenMode)
     }
 
+    fun deactivateAllModes() {
+        for (mode in zenModeRepository.getModes()) {
+            if (mode.isActive) {
+                deactivateMode(mode)
+            }
+        }
+    }
+
     private val zenDuration
         get() = notificationSettingsRepository.zenDuration.value
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index f98ad45..3bd2721 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -196,15 +196,17 @@
         uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
             ringerButton?.let {
                 val view = getChildAt(count - index - 1)
+                val isOpen = uiModel.drawerState is RingerDrawerState.Open
                 if (index == uiModel.currentButtonIndex) {
                     view.bindDrawerButton(
-                        uiModel.selectedButton,
+                        if (isOpen) it else uiModel.selectedButton,
                         viewModel,
+                        isOpen,
                         isSelected = true,
                         isAnimated = isAnimated,
                     )
                 } else {
-                    view.bindDrawerButton(it, viewModel, isAnimated)
+                    view.bindDrawerButton(it, viewModel, isOpen, isAnimated = isAnimated)
                 }
             }
         }
@@ -214,12 +216,22 @@
     private fun View.bindDrawerButton(
         buttonViewModel: RingerButtonViewModel,
         viewModel: VolumeDialogRingerDrawerViewModel,
+        isOpen: Boolean,
         isSelected: Boolean = false,
         isAnimated: Boolean = false,
     ) {
+        val ringerContentDesc = context.getString(buttonViewModel.contentDescriptionResId)
         with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
             setImageResource(buttonViewModel.imageResId)
-            contentDescription = context.getString(buttonViewModel.contentDescriptionResId)
+            contentDescription =
+                if (isSelected && !isOpen) {
+                    context.getString(
+                        R.string.volume_ringer_drawer_closed_content_description,
+                        ringerContentDesc,
+                    )
+                } else {
+                    ringerContentDesc
+                }
             if (isSelected && !isAnimated) {
                 setBackgroundResource(R.drawable.volume_drawer_selection_bg)
                 setColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index e565de5..02747d7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
 
 import android.content.Context
+import android.graphics.Color as GraphicsColor
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.Flags
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Color
 import com.android.systemui.common.shared.model.Icon
@@ -77,7 +79,13 @@
                 ConnectedDeviceViewModel(
                     label = label,
                     labelColor =
-                        Color.Attribute(com.android.internal.R.attr.materialColorOnSurfaceVariant),
+                        if (Flags.volumeRedesign()) {
+                            Color.Attribute(com.android.internal.R.attr.materialColorOnSurface)
+                        } else {
+                            Color.Attribute(
+                                com.android.internal.R.attr.materialColorOnSurfaceVariant
+                            )
+                        },
                     deviceName =
                         if (mediaOutputModel.isInAudioSharing) {
                             context.getString(R.string.audio_sharing_description)
@@ -96,11 +104,7 @@
                         },
                 )
             }
-            .stateIn(
-                coroutineScope,
-                SharingStarted.Eagerly,
-                null,
-            )
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
 
     val deviceIconViewModel: StateFlow<DeviceIconViewModel?> =
         mediaOutputComponentInteractor.mediaOutputModel
@@ -121,7 +125,15 @@
                         icon = icon,
                         iconColor =
                             if (mediaOutputModel.canOpenAudioSwitcher) {
-                                Color.Attribute(com.android.internal.R.attr.materialColorSurface)
+                                if (Flags.volumeRedesign()) {
+                                    Color.Attribute(
+                                        com.android.internal.R.attr.materialColorOnPrimary
+                                    )
+                                } else {
+                                    Color.Attribute(
+                                        com.android.internal.R.attr.materialColorSurface
+                                    )
+                                }
                             } else {
                                 Color.Attribute(
                                     com.android.internal.R.attr.materialColorSurfaceContainerHighest
@@ -129,7 +141,15 @@
                             },
                         backgroundColor =
                             if (mediaOutputModel.canOpenAudioSwitcher) {
-                                Color.Attribute(com.android.internal.R.attr.materialColorSecondary)
+                                if (Flags.volumeRedesign()) {
+                                    Color.Attribute(
+                                        com.android.internal.R.attr.materialColorPrimary
+                                    )
+                                } else {
+                                    Color.Attribute(
+                                        com.android.internal.R.attr.materialColorSecondary
+                                    )
+                                }
                             } else {
                                 Color.Attribute(com.android.internal.R.attr.materialColorOutline)
                             },
@@ -139,38 +159,29 @@
                         icon = icon,
                         iconColor =
                             if (mediaOutputModel.canOpenAudioSwitcher) {
-                                Color.Attribute(
-                                    com.android.internal.R.attr.materialColorOnSurfaceVariant
-                                )
+                                if (Flags.volumeRedesign()) {
+                                    Color.Attribute(
+                                        com.android.internal.R.attr.materialColorPrimary
+                                    )
+                                } else {
+                                    Color.Attribute(
+                                        com.android.internal.R.attr.materialColorOnSurfaceVariant
+                                    )
+                                }
                             } else {
                                 Color.Attribute(com.android.internal.R.attr.materialColorOutline)
                             },
-                        backgroundColor =
-                            if (mediaOutputModel.canOpenAudioSwitcher) {
-                                Color.Attribute(com.android.internal.R.attr.materialColorSurface)
-                            } else {
-                                Color.Attribute(
-                                    com.android.internal.R.attr.materialColorSurfaceContainerHighest
-                                )
-                            },
+                        backgroundColor = Color.Loaded(GraphicsColor.TRANSPARENT),
                     )
                 }
             }
-            .stateIn(
-                coroutineScope,
-                SharingStarted.Eagerly,
-                null,
-            )
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
 
     val enabled: StateFlow<Boolean> =
         mediaOutputComponentInteractor.mediaOutputModel
             .filterData()
             .map { it.canOpenAudioSwitcher }
-            .stateIn(
-                coroutineScope,
-                SharingStarted.Eagerly,
-                true,
-            )
+            .stateIn(coroutineScope, SharingStarted.Eagerly, true)
 
     fun onBarClick(expandable: Expandable?) {
         uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
@@ -178,7 +189,7 @@
             mediaOutputComponentInteractor.mediaOutputModel.value
         actionsInteractor.onBarClick(
             (result as? Result.Data<MediaOutputComponentModel>)?.data,
-            expandable
+            expandable,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 073781e..0ec71c2 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -48,7 +48,6 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -60,6 +59,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.wm.shell.dagger.WMComponent;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.onehanded.OneHanded;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index ae94544..f347d48 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -90,6 +90,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.clearInvocations
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -536,6 +537,7 @@
                 .onZenDataChanged(
                     eq(ZenData(ZenMode.IMPORTANT_INTERRUPTIONS, R.string::dnd_is_on.name))
                 )
+            clearInvocations(events)
 
             zenModeRepository.deactivateMode(dndModeId)
             runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 7c08928..11199cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -44,6 +44,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioManager;
 import android.media.AudioRecordingConfiguration;
@@ -593,11 +594,11 @@
 
         //untrusted receiver access
         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID,
-                TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
+                TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true,
                 AppOpsManager.ATTRIBUTION_FLAG_RECEIVER, TEST_CHAIN_ID);
         //untrusted intermediary access
         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID,
-                TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
+                TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true,
                 AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY, TEST_CHAIN_ID);
         assertTrue(mController.getActiveAppOps().isEmpty());
     }
@@ -606,11 +607,11 @@
     public void testTrustedChainUsagesKept() {
         //untrusted accessor access
         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID,
-                TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
+                TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true,
                 AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR, TEST_CHAIN_ID);
         //trusted access
         mController.onOpActiveChanged(AppOpsManager.OPSTR_CAMERA, TEST_UID,
-                TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
+                TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true,
                 AppOpsManager.ATTRIBUTION_FLAG_RECEIVER | AppOpsManager.ATTRIBUTION_FLAG_TRUSTED,
                 TEST_CHAIN_ID);
         assertEquals(2, mController.getActiveAppOps().size());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 194f456..38acd23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -137,6 +137,7 @@
 import com.android.systemui.util.settings.SystemSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
 import kotlinx.coroutines.CoroutineDispatcher;
@@ -157,6 +158,10 @@
 @TestableLooper.RunWithLooper
 @SmallTest
 public class KeyguardViewMediatorTest extends SysuiTestCase {
+
+    private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS =
+            Flags.ensureKeyguardDoesTransitionStarting();
+
     private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     private KeyguardViewMediator mViewMediator;
 
@@ -1164,6 +1169,29 @@
      */
     private void assertATMSLockScreenShowing(boolean showing)
             throws RemoteException {
+
+        if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+            // ATMS is called via bgExecutor, so make sure to run all of those calls first.
+            processAllMessagesAndBgExecutorMessages();
+
+            final InOrder orderedSetLockScreenShownCalls = inOrder(mKeyguardTransitions);
+            final ArgumentCaptor<Boolean> showingCaptor = ArgumentCaptor.forClass(Boolean.class);
+            orderedSetLockScreenShownCalls
+                    .verify(mKeyguardTransitions, atLeastOnce())
+                    .startKeyguardTransition(showingCaptor.capture(), anyBoolean());
+
+            // The captor will have the most recent startKeyguardTransition call's value.
+            assertEquals(showing, showingCaptor.getValue());
+
+            // We're now just after the last startKeyguardTransition call. If we expect the
+            // lockscreen to be showing, ensure that we didn't subsequently ask for it to go away.
+            if (showing) {
+                orderedSetLockScreenShownCalls.verify(mKeyguardTransitions, never())
+                        .startKeyguardTransition(eq(false), anyBoolean());
+            }
+            return;
+        }
+
         // ATMS is called via bgExecutor, so make sure to run all of those calls first.
         processAllMessagesAndBgExecutorMessages();
 
@@ -1192,6 +1220,20 @@
         // ATMS is called via bgExecutor, so make sure to run all of those calls first.
         processAllMessagesAndBgExecutorMessages();
 
+        if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+            final InOrder orderedGoingAwayCalls = inOrder(mKeyguardTransitions);
+            orderedGoingAwayCalls.verify(mKeyguardTransitions, atLeastOnce())
+                    .startKeyguardTransition(eq(false) /* keyguardShowing */,
+                            eq(false) /* aodShowing */);
+
+            // Advance the inOrder to just past the last goingAway call. Let's make sure we didn't
+            // re-show the lockscreen, which would cancel going away.
+            orderedGoingAwayCalls.verify(mKeyguardTransitions, never())
+                    .startKeyguardTransition(eq(true) /* keyguardShowing */,
+                            anyBoolean() /* aodShowing */);
+            return;
+        }
+
         final InOrder orderedGoingAwayCalls = inOrder(mActivityTaskManagerService);
         orderedGoingAwayCalls.verify(mActivityTaskManagerService, atLeastOnce())
                 .keyguardGoingAway(anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 7634490..728f418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -655,14 +655,14 @@
             // CDMA roaming is off, GSM roaming is off
             whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
             cb.onDisplayInfoChanged(
-                TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false)
+                TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false, false, false)
             )
 
             assertThat(latest).isFalse()
 
             // CDMA roaming is off, GSM roaming is on
             cb.onDisplayInfoChanged(
-                TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true)
+                TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true, false, false)
             )
 
             assertThat(latest).isTrue()
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
index cc0597b..76fc611 100644
--- a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
@@ -30,6 +30,7 @@
 import android.os.IBinder
 import android.os.UserHandle
 import android.util.ArrayMap
+import android.view.Display
 import android.view.KeyEvent
 import com.android.internal.logging.InstanceId
 import com.android.internal.statusbar.IAddTileResultCallback
@@ -95,6 +96,90 @@
             )
         )
 
+    var statusBarIconsSecondaryDisplay =
+        ArrayMap<String, StatusBarIcon>().also {
+            it["slot1"] = mock<StatusBarIcon>()
+            it["slot2"] = mock<StatusBarIcon>()
+        }
+    var disabledFlags1SecondaryDisplay = 12345678
+    var appearanceSecondaryDisplay = 1234
+    var appearanceRegionsSecondaryDisplay =
+        arrayOf(
+            AppearanceRegion(
+                /* appearance = */ 123,
+                /* bounds = */ Rect(/* left= */ 4, /* top= */ 3, /* right= */ 2, /* bottom= */ 1),
+            ),
+            AppearanceRegion(
+                /* appearance = */ 345,
+                /* bounds = */ Rect(/* left= */ 1, /* top= */ 2, /* right= */ 3, /* bottom= */ 4),
+            ),
+        )
+    var imeWindowVisSecondaryDisplay = 9876
+    var imeBackDispositionSecondaryDisplay = 654
+    var showImeSwitcherSecondaryDisplay = true
+    var disabledFlags2SecondaryDisplay = 87654321
+    var navbarColorManagedByImeSecondaryDisplay = true
+    var behaviorSecondaryDisplay = 234
+    var requestedVisibleTypesSecondaryDisplay = 345
+    var packageNameSecondaryDisplay = "fake.bar.ser.vice"
+    var transientBarTypesSecondaryDisplay = 0
+    var letterboxDetailsSecondaryDisplay =
+        arrayOf(
+            LetterboxDetails(
+                /* letterboxInnerBounds = */ Rect(
+                    /* left= */ 5,
+                    /* top= */ 6,
+                    /* right= */ 7,
+                    /* bottom= */ 8,
+                ),
+                /* letterboxFullBounds = */ Rect(
+                    /* left= */ 1,
+                    /* top= */ 2,
+                    /* right= */ 3,
+                    /* bottom= */ 4,
+                ),
+                /* appAppearance = */ 123,
+            )
+        )
+
+    private val defaultRegisterStatusBarResult
+        get() =
+            RegisterStatusBarResult(
+                statusBarIcons,
+                disabledFlags1,
+                appearance,
+                appearanceRegions,
+                imeWindowVis,
+                imeBackDisposition,
+                showImeSwitcher,
+                disabledFlags2,
+                navbarColorManagedByIme,
+                behavior,
+                requestedVisibleTypes,
+                packageName,
+                transientBarTypes,
+                letterboxDetails,
+            )
+
+    private val registerStatusBarResultSecondaryDisplay
+        get() =
+            RegisterStatusBarResult(
+                statusBarIconsSecondaryDisplay,
+                disabledFlags1SecondaryDisplay,
+                appearanceSecondaryDisplay,
+                appearanceRegionsSecondaryDisplay,
+                imeWindowVisSecondaryDisplay,
+                imeBackDispositionSecondaryDisplay,
+                showImeSwitcherSecondaryDisplay,
+                disabledFlags2SecondaryDisplay,
+                navbarColorManagedByImeSecondaryDisplay,
+                behaviorSecondaryDisplay,
+                requestedVisibleTypesSecondaryDisplay,
+                packageNameSecondaryDisplay,
+                transientBarTypesSecondaryDisplay,
+                letterboxDetailsSecondaryDisplay,
+            )
+
     override fun expandNotificationsPanel() {}
 
     override fun collapsePanels() {}
@@ -140,21 +225,16 @@
 
     override fun registerStatusBar(callbacks: IStatusBar): RegisterStatusBarResult {
         registeredStatusBar = callbacks
-        return RegisterStatusBarResult(
-            statusBarIcons,
-            disabledFlags1,
-            appearance,
-            appearanceRegions,
-            imeWindowVis,
-            imeBackDisposition,
-            showImeSwitcher,
-            disabledFlags2,
-            navbarColorManagedByIme,
-            behavior,
-            requestedVisibleTypes,
-            packageName,
-            transientBarTypes,
-            letterboxDetails,
+        return defaultRegisterStatusBarResult
+    }
+
+    override fun registerStatusBarForAllDisplays(
+        callbacks: IStatusBar
+    ): Map<String, RegisterStatusBarResult> {
+        registeredStatusBar = callbacks
+        return mapOf(
+            DEFAULT_DISPLAY_ID.toString() to defaultRegisterStatusBarResult,
+            SECONDARY_DISPLAY_ID.toString() to registerStatusBarResultSecondaryDisplay,
         )
     }
 
@@ -352,4 +432,9 @@
     override fun unregisterNearbyMediaDevicesProvider(provider: INearbyMediaDevicesProvider) {}
 
     override fun showRearDisplayDialog(currentBaseState: Int) {}
+
+    companion object {
+        const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+        const val SECONDARY_DISPLAY_ID = 2
+    }
 }
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt
similarity index 63%
copy from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt
index 0589bf8..470a8e4 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt
@@ -5,7 +5,7 @@
  * 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
+ *      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,
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package android.app.ondeviceintelligence;
+package com.android.systemui.biometrics
 
-/**
-  * @hide
-  */
-parcelable FeatureDetails;
+import android.hardware.fingerprint.FingerprintManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.fingerprintManager by Kosmos.Fixture { mock<FingerprintManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
index ae592b9..646c190 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
@@ -17,20 +17,19 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.content.applicationContext
-import android.hardware.fingerprint.FingerprintManager
 import com.android.systemui.biometrics.authController
+import com.android.systemui.biometrics.fingerprintManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
-import com.android.systemui.util.mockito.mock
 
 val Kosmos.udfpsOverlayInteractor by Fixture {
     UdfpsOverlayInteractor(
         context = applicationContext,
         authController = authController,
         selectedUserInteractor = selectedUserInteractor,
-        fingerprintManager = mock<FingerprintManager>(),
+        fingerprintManager = fingerprintManager,
         scope = applicationCoroutineScope,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
index 2ecfb45..3c37101 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
 import javax.inject.Provider
 
@@ -26,5 +27,6 @@
         ModesTileUserActionInteractor(
             qsTileIntentUserInputHandler,
             Provider { modesDialogDelegate }.get(),
+            zenModeInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 8712b02..22f8767 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -34,12 +34,12 @@
         const val DISPLAY_ID = Display.DEFAULT_DISPLAY
     }
 
-    override val defaultDisplay: FakeStatusBarModePerDisplayRepository =
-        FakeStatusBarModePerDisplayRepository()
+    private val perDisplayRepos = mutableMapOf<Int, FakeStatusBarModePerDisplayRepository>()
 
-    override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository {
-        return defaultDisplay
-    }
+    override val defaultDisplay: FakeStatusBarModePerDisplayRepository = forDisplay(DISPLAY_ID)
+
+    override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository =
+        perDisplayRepos.computeIfAbsent(displayId) { FakeStatusBarModePerDisplayRepository() }
 }
 
 class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index 68d08e2..bbccbb1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.settingslib.notification.modes.zenIconLoader
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.backgroundScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
 import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
@@ -32,6 +33,7 @@
         zenModeRepository = zenModeRepository,
         notificationSettingsRepository = notificationSettingsRepository,
         bgDispatcher = testDispatcher,
+        backgroundScope = backgroundScope,
         iconLoader = zenIconLoader,
         deviceProvisioningRepository = deviceProvisioningRepository,
         userSetupRepository = userSetupRepository,
diff --git a/proto/Android.bp b/proto/Android.bp
index a5e1335..feaa6d2 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -25,6 +25,10 @@
             static_libs: ["libprotobuf-java-nano"],
         },
     },
+    apex_available: [
+        "com.android.neuralnetworks",
+        "//apex_available:platform",
+    ],
 }
 
 java_library_static {
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index 9eff20a..a332633 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -132,36 +132,27 @@
 
     @Override
     public Looper getMainLooper() {
-        Objects.requireNonNull(mMainThread,
-                "Test must request setProvideMainThread() via RavenwoodConfig");
         return mMainThread.getLooper();
     }
 
     @Override
     public Handler getMainThreadHandler() {
-        Objects.requireNonNull(mMainThread,
-                "Test must request setProvideMainThread() via RavenwoodConfig");
         return mMainThread.getThreadHandler();
     }
 
     @Override
     public Executor getMainExecutor() {
-        Objects.requireNonNull(mMainThread,
-                "Test must request setProvideMainThread() via RavenwoodConfig");
         return mMainThread.getThreadExecutor();
     }
 
     @Override
     public String getPackageName() {
-        return Objects.requireNonNull(mPackageName,
-                "Test must request setPackageName() (or setTargetPackageName())"
-                + " via RavenwoodConfig");
+        return mPackageName;
     }
 
     @Override
     public String getOpPackageName() {
-        return Objects.requireNonNull(mPackageName,
-                "Test must request setPackageName() via RavenwoodConfig");
+        return mPackageName;
     }
 
     @Override
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
deleted file mode 100644
index 3ed0f50..0000000
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ /dev/null
@@ -1,139 +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 android.platform.test.ravenwood;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * @deprecated This class will be removed. Reach out to g/ravenwood if you need any features in it.
- */
-@Deprecated
-public final class RavenwoodConfig {
-    /**
-     * Use this to mark a field as the configuration.
-     * @hide
-     */
-    @Target({ElementType.FIELD})
-    @Retention(RetentionPolicy.RUNTIME)
-    public @interface Config {
-    }
-
-    /**
-     * Stores internal states / methods associated with this config that's only needed in
-     * junit-impl.
-     */
-    private RavenwoodConfig() {
-    }
-
-    /**
-     * Return if the current process is running on a Ravenwood test environment.
-     */
-    public static boolean isOnRavenwood() {
-        return RavenwoodRule.isOnRavenwood();
-    }
-
-    public static class Builder {
-        private final RavenwoodConfig mConfig = new RavenwoodConfig();
-
-        public Builder() {
-        }
-
-        /**
-         * @deprecated no longer used. We always use an app UID.
-         */
-        @Deprecated
-        public Builder setProcessSystem() {
-            return this;
-        }
-
-        /**
-         * @deprecated no longer used. We always use an app UID.
-         */
-        @Deprecated
-        public Builder setProcessApp() {
-            return this;
-        }
-
-        /**
-         * @deprecated no longer used. Package name is set in the build file. (for now)
-         */
-        @Deprecated
-        public Builder setPackageName(@NonNull String packageName) {
-            return this;
-        }
-
-        /**
-         * @deprecated no longer used. Package name is set in the build file. (for now)
-         */
-        @Deprecated
-        public Builder setTargetPackageName(@NonNull String packageName) {
-            return this;
-        }
-
-
-        /**
-         * @deprecated no longer used. Target SDK level is set in the build file. (for now)
-         */
-        @Deprecated
-        public Builder setTargetSdkLevel(int sdkLevel) {
-            return this;
-        }
-
-        /**
-         * @deprecated no longer used. Main thread is always available.
-         */
-        @Deprecated
-        public Builder setProvideMainThread(boolean provideMainThread) {
-            return this;
-        }
-
-        /**
-         * @deprecated Use {@link RavenwoodRule.Builder#setSystemPropertyImmutable(String, Object)}
-         */
-        @Deprecated
-        public Builder setSystemPropertyImmutable(@NonNull String key,
-                @Nullable Object value) {
-            return this;
-        }
-
-        /**
-         * @deprecated Use {@link RavenwoodRule.Builder#setSystemPropertyMutable(String, Object)}
-         */
-        @Deprecated
-        public Builder setSystemPropertyMutable(@NonNull String key,
-                @Nullable Object value) {
-            return this;
-        }
-
-        /**
-         * @deprecated no longer used. All supported services are available.
-         */
-        @Deprecated
-        public Builder setServicesRequired(@NonNull Class<?>... services) {
-            return this;
-        }
-
-        public RavenwoodConfig build() {
-            return mConfig;
-        }
-    }
-}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index d8cde0e..ffe5f6c 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -36,10 +36,8 @@
 import java.util.regex.Pattern;
 
 /**
- * @deprecated This class is undergoing a major change. Reach out to g/ravenwood if you need
- * any featues in it.
+ * Reach out to g/ravenwood if you need any features in it.
  */
-@Deprecated
 public final class RavenwoodRule implements TestRule {
     private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
 
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
deleted file mode 100644
index 306c2b39..0000000
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
+++ /dev/null
@@ -1,42 +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.ravenwoodtest.bivalenttest;
-
-import static android.platform.test.ravenwood.RavenwoodConfig.isOnRavenwood;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test to make sure the config field is used.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodConfigTest {
-    private static final String PACKAGE_NAME = "com.android.ravenwoodtest.bivalenttest";
-
-    @Test
-    public void testConfig() {
-        assumeTrue(isOnRavenwood());
-        assertEquals(PACKAGE_NAME,
-                InstrumentationRegistry.getInstrumentation().getContext().getPackageName());
-    }
-}
diff --git a/services/Android.bp b/services/Android.bp
index fc0bb33..a7cb9bb 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -210,6 +210,35 @@
     },
 }
 
+soong_config_module_type {
+    name: "ondeviceintelligence_module_java_defaults",
+    module_type: "java_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "release_ondevice_intelligence_module",
+        "release_ondevice_intelligence_platform",
+    ],
+    properties: [
+        "libs",
+        "srcs",
+        "static_libs",
+    ],
+}
+
+// Conditionally add ondeviceintelligence stubs library
+ondeviceintelligence_module_java_defaults {
+    name: "ondeviceintelligence_conditionally",
+    soong_config_variables: {
+        release_ondevice_intelligence_module: {
+            libs: ["service-ondeviceintelligence.stubs.system_server"],
+        },
+        release_ondevice_intelligence_platform: {
+            srcs: [":service-ondeviceintelligence-sources"],
+            static_libs: ["modules-utils-backgroundthread"],
+        },
+    },
+}
+
 // merge all required services into one jar
 // ============================================================
 soong_config_module_type {
@@ -236,6 +265,7 @@
         "services_java_defaults",
         "art_profile_java_defaults",
         "services_crashrecovery_stubs_conditionally",
+        "ondeviceintelligence_conditionally",
     ],
     installable: true,
 
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index f13e229..c17c340 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -24,12 +24,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
+import android.app.appfunctions.AppFunctionException;
 import android.app.appfunctions.AppFunctionManager;
 import android.app.appfunctions.AppFunctionManagerHelper;
 import android.app.appfunctions.AppFunctionRuntimeMetadata;
 import android.app.appfunctions.AppFunctionStaticMetadataHelper;
 import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
-import android.app.appfunctions.AppFunctionException;
 import android.app.appfunctions.IAppFunctionEnabledCallback;
 import android.app.appfunctions.IAppFunctionManager;
 import android.app.appfunctions.IAppFunctionService;
@@ -158,8 +158,7 @@
         } catch (SecurityException exception) {
             safeExecuteAppFunctionCallback.onError(
                     new AppFunctionException(
-                            AppFunctionException.ERROR_DENIED,
-                            exception.getMessage()));
+                            AppFunctionException.ERROR_DENIED, exception.getMessage()));
             return null;
         }
 
@@ -195,12 +194,12 @@
             @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback,
             @NonNull IBinder callerBinder) {
         UserHandle targetUser = requestInternal.getUserHandle();
-        // TODO(b/354956319): Add and honor the new enterprise policies.
-        if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
+        UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+        if (!mCallerValidator.verifyEnterprisePolicyIsAllowed(callingUser, targetUser)) {
             safeExecuteAppFunctionCallback.onError(
-                    new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR,
-                            "Cannot run on a device with a device owner or from the managed"
-                                    + " profile."));
+                    new AppFunctionException(
+                            AppFunctionException.ERROR_ENTERPRISE_POLICY_DISALLOWED,
+                            "Cannot run on a user with a restricted enterprise policy"));
             return;
         }
 
@@ -442,7 +441,8 @@
         if (!bindServiceResult) {
             Slog.e(TAG, "Failed to bind to the AppFunctionService");
             safeExecuteAppFunctionCallback.onError(
-                    new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR,
+                    new AppFunctionException(
+                            AppFunctionException.ERROR_SYSTEM_ERROR,
                             "Failed to bind the AppFunctionService."));
         }
     }
@@ -495,8 +495,7 @@
             return;
         }
         FutureGlobalSearchSession futureGlobalSearchSession =
-                new FutureGlobalSearchSession(
-                        perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR);
+                new FutureGlobalSearchSession(perUserAppSearchManager, THREAD_POOL_EXECUTOR);
         AppFunctionMetadataObserver appFunctionMetadataObserver =
                 new AppFunctionMetadataObserver(
                         user.getUserHandle(),
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
index 5393b93..6191767 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -81,10 +81,12 @@
             @NonNull String functionId);
 
     /**
-     * Checks if the user is organization managed.
+     * Checks if the app function policy is allowed.
      *
+     * @param callingUser The current calling user.
      * @param targetUser The user which the caller is requesting to execute as.
-     * @return Whether the user is organization managed.
+     * @return Whether the app function policy is allowed.
      */
-    boolean isUserOrganizationManaged(@NonNull UserHandle targetUser);
+    boolean verifyEnterprisePolicyIsAllowed(
+            @NonNull UserHandle callingUser, @NonNull UserHandle targetUser);
 }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index e85a70d..69481c3 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -28,6 +28,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.AppFunctionsPolicy;
 import android.app.appsearch.AppSearchBatchResult;
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchManager.SearchContext;
@@ -39,7 +40,6 @@
 import android.os.Binder;
 import android.os.Process;
 import android.os.UserHandle;
-import android.os.UserManager;
 
 import com.android.internal.infra.AndroidFuture;
 
@@ -124,8 +124,7 @@
         FutureAppSearchSession futureAppSearchSession =
                 new FutureAppSearchSessionImpl(
                         Objects.requireNonNull(
-                                mContext
-                                        .createContextAsUser(targetUser, 0)
+                                mContext.createContextAsUser(targetUser, 0)
                                         .getSystemService(AppSearchManager.class)),
                         THREAD_POOL_EXECUTOR,
                         new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build());
@@ -168,13 +167,16 @@
     }
 
     @Override
-    public boolean isUserOrganizationManaged(@NonNull UserHandle targetUser) {
-        if (Objects.requireNonNull(mContext.getSystemService(DevicePolicyManager.class))
-                .isDeviceManaged()) {
-            return true;
-        }
-        return Objects.requireNonNull(mContext.getSystemService(UserManager.class))
-                .isManagedProfile(targetUser.getIdentifier());
+    public boolean verifyEnterprisePolicyIsAllowed(
+            @NonNull UserHandle callingUser, @NonNull UserHandle targetUser) {
+        @AppFunctionsPolicy
+        int callingUserPolicy = getDevicePolicyManagerAsUser(callingUser).getAppFunctionsPolicy();
+        @AppFunctionsPolicy
+        int targetUserPolicy = getDevicePolicyManagerAsUser(targetUser).getAppFunctionsPolicy();
+        boolean isSameUser = callingUser.equals(targetUser);
+
+        return isAppFunctionPolicyAllowed(targetUserPolicy, isSameUser)
+                && isAppFunctionPolicyAllowed(callingUserPolicy, isSameUser);
     }
 
     /**
@@ -264,4 +266,18 @@
             return Process.INVALID_UID;
         }
     }
+
+    private boolean isAppFunctionPolicyAllowed(
+            @AppFunctionsPolicy int userPolicy, boolean isSameUser) {
+        return switch (userPolicy) {
+            case DevicePolicyManager.APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY -> true;
+            case DevicePolicyManager.APP_FUNCTIONS_DISABLED_CROSS_PROFILE -> isSameUser;
+            default -> false;
+        };
+    }
+
+    private DevicePolicyManager getDevicePolicyManagerAsUser(@NonNull UserHandle targetUser) {
+        return mContext.createContextAsUser(targetUser, /* flags= */ 0)
+                .getSystemService(DevicePolicyManager.class);
+    }
 }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index ffa259b..371306f 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -127,6 +127,7 @@
         "android.hardware.power-java_shared",
         "latest_android_hardware_broadcastradio_java_static",
         "services_crashrecovery_stubs_conditionally",
+        "ondeviceintelligence_conditionally",
     ],
     srcs: [
         ":android.hardware.tv.hdmi.connection-V1-java-source",
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index e57b009..eba9a25 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2166,7 +2166,11 @@
             overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
         }
         boolean isRoaming = telephonyDisplayInfo.isRoaming();
-        return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming);
+        boolean isNtn = telephonyDisplayInfo.isNtn();
+        boolean isSatelliteConstrainedData =
+                telephonyDisplayInfo.isSatelliteConstrainedData();
+        return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming,
+                isNtn, isSatelliteConstrainedData);
     }
 
     public void notifyCallForwardingChanged(boolean cfi) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c27126a..aea24d9 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1101,6 +1101,9 @@
 
     /** StatsPullAtomCallback for pulling BatteryUsageStats data. */
     private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
+        private static final long BATTERY_USAGE_STATS_PER_UID_MAX_STATS_AGE =
+                TimeUnit.HOURS.toMillis(2);
+
         @Override
         public int onPullAtom(int atomTag, List<StatsEvent> data) {
             final BatteryUsageStats bus;
@@ -1168,7 +1171,8 @@
                             .setMinConsumedPowerThreshold(minConsumedPowerThreshold);
 
                     if (isBatteryUsageStatsAccumulationSupported()) {
-                        query.accumulated();
+                        query.accumulated()
+                                .setMaxStatsAgeMs(BATTERY_USAGE_STATS_PER_UID_MAX_STATS_AGE);
                     }
 
                     bus = getBatteryUsageStats(List.of(query.build())).get(0);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d0153d8..6f83d0c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -42,6 +42,7 @@
 import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
 import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
 import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
+import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -146,7 +147,9 @@
         "aaos_window_triage",
         "aaos_audio_triage",
         "aaos_power_triage",
+        "aaos_storage_triage",
         "aaos_sdv",
+        "aaos_vac_triage",
         "accessibility",
         "android_core_networking",
         "android_health_services",
@@ -462,16 +465,38 @@
      * @param requests: request proto output stream
      * @return aconfigd socket return as proto input stream
      */
-    static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
+    static void sendAconfigdRequests(ProtoOutputStream requests) {
+        ProtoInputStream returns = sendAconfigdRequests("aconfigd_system", requests);
+        try {
+          parseAndLogAconfigdReturn(returns);
+        } catch (IOException ioe) {
+          logErr("failed to parse aconfigd return", ioe);
+        }
+        if (enableAconfigdFromMainline()) {
+            returns = sendAconfigdRequests("aconfigd_mainline", requests);
+            try {
+              parseAndLogAconfigdReturn(returns);
+            } catch (IOException ioe) {
+              logErr("failed to parse aconfigd return", ioe);
+            }
+        }
+    }
+
+    /**
+     * apply flag local override in aconfig new storage
+     * @param socketName: the socket to send to
+     * @param requests: request proto output stream
+     * @return aconfigd socket return as proto input stream
+     */
+    static ProtoInputStream sendAconfigdRequests(String socketName, ProtoOutputStream requests) {
         // connect to aconfigd socket
         LocalSocket client = new LocalSocket();
-        String socketName = "aconfigd_system";
         try {
             client.connect(new LocalSocketAddress(
                 socketName, LocalSocketAddress.Namespace.RESERVED));
-            Slog.d(TAG, "connected to aconfigd socket");
+            Slog.d(TAG, "connected to " + socketName + " socket");
         } catch (IOException ioe) {
-            logErr("failed to connect to aconfigd socket", ioe);
+            logErr("failed to connect to " + socketName + " socket", ioe);
             return null;
         }
 
@@ -490,9 +515,9 @@
             byte[] requests_bytes = requests.getBytes();
             outputStream.writeInt(requests_bytes.length);
             outputStream.write(requests_bytes, 0, requests_bytes.length);
-            Slog.d(TAG, "flag override requests sent to aconfigd");
+            Slog.d(TAG, "flag override requests sent to " + socketName);
         } catch (IOException ioe) {
-            logErr("failed to send requests to aconfigd", ioe);
+            logErr("failed to send requests to " + socketName, ioe);
             return null;
         }
 
@@ -500,10 +525,10 @@
         try {
             int num_bytes = inputStream.readInt();
             ProtoInputStream returns = new ProtoInputStream(inputStream);
-            Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd");
+            Slog.d(TAG, "received " + num_bytes + " bytes back from " + socketName);
             return returns;
         } catch (IOException ioe) {
-            logErr("failed to read requests return from aconfigd", ioe);
+            logErr("failed to read requests return from " + socketName, ioe);
             return null;
         }
     }
@@ -644,15 +669,8 @@
           return;
         }
 
-        // send requests to aconfigd and obtain the return byte buffer
-        ProtoInputStream returns = sendAconfigdRequests(requests);
-
-        // deserialize back using proto input stream
-        try {
-          parseAndLogAconfigdReturn(returns);
-        } catch (IOException ioe) {
-            logErr("failed to parse aconfigd return", ioe);
-        }
+        // send requests to aconfigd
+        sendAconfigdRequests(requests);
     }
 
     public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -762,15 +780,8 @@
           return;
         }
 
-        // send requests to aconfigd and obtain the return
-        ProtoInputStream returns = sendAconfigdRequests(requests);
-
-        // deserialize back using proto input stream
-        try {
-            parseAndLogAconfigdReturn(returns);
-        } catch (IOException ioe) {
-            logErr("failed to parse aconfigd return", ioe);
-        }
+        // send requests to aconfigd
+        sendAconfigdRequests(requests);
     }
 
     /**
diff --git a/services/core/java/com/android/server/content/SyncLogger.java b/services/core/java/com/android/server/content/SyncLogger.java
index fc20ef20..7154bc4 100644
--- a/services/core/java/com/android/server/content/SyncLogger.java
+++ b/services/core/java/com/android/server/content/SyncLogger.java
@@ -73,6 +73,8 @@
      */
     public static synchronized SyncLogger getInstance() {
         if (sInstance == null) {
+            // Always default to the sync logger for now (see b/381957278#comment8).
+            /*
             final String flag = SystemProperties.get("debug.synclog");
             final boolean enable =
                     (Build.IS_DEBUGGABLE
@@ -83,6 +85,8 @@
             } else {
                 sInstance = new SyncLogger();
             }
+            */
+            sInstance = new SyncLogger();
         }
         return sInstance;
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5c62995..452dc5f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -47,6 +47,7 @@
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.ROOT_UID;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
+import static android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY;
 import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
 import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
 import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
@@ -71,6 +72,7 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -534,6 +536,8 @@
     private final boolean mExtraDisplayEventLogging;
     private final String mExtraDisplayLoggingPackageName;
 
+    private boolean mMirrorBuiltInDisplay;
+
     private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -782,7 +786,11 @@
                 }
                 dpc.onSwitchUser(newUserId, userSerial, newBrightness);
             });
-            handleSettingsChange();
+            handleMinimalPostProcessingAllowedSettingChange();
+
+            if (mFlags.isDisplayContentModeManagementEnabled()) {
+                updateMirrorBuiltInDisplaySettingLocked();
+            }
         }
     }
 
@@ -825,12 +833,15 @@
             // relevant configuration should be in place.
             recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
 
-            updateSettingsLocked();
+            updateMinimalPostProcessingAllowedSettingLocked();
             updateUserDisabledHdrTypesFromSettingsLocked();
             updateUserPreferredDisplayModeSettingsLocked();
             if (mIsHdrOutputControlEnabled) {
                 updateHdrConversionModeSettingsLocked();
             }
+            if (mFlags.isDisplayContentModeManagementEnabled()) {
+                updateMirrorBuiltInDisplaySettingLocked();
+            }
         }
 
         mDisplayModeDirector.setDesiredDisplayModeSpecsListener(
@@ -1180,27 +1191,61 @@
             mContext.getContentResolver().registerContentObserver(
                     Settings.Secure.getUriFor(
                         Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED), false, this);
+
+            if (mFlags.isDisplayContentModeManagementEnabled()) {
+                mContext.getContentResolver().registerContentObserver(
+                        Settings.Secure.getUriFor(
+                                MIRROR_BUILT_IN_DISPLAY), false, this, UserHandle.USER_ALL);
+            }
         }
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            handleSettingsChange();
+            if (Settings.Secure.getUriFor(
+                    Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED).equals(uri)) {
+                handleMinimalPostProcessingAllowedSettingChange();
+                return;
+            }
+
+            if (Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY).equals(uri)) {
+                if (mFlags.isDisplayContentModeManagementEnabled()) {
+                    updateMirrorBuiltInDisplaySettingLocked();
+                }
+                return;
+            }
         }
     }
 
-    private void handleSettingsChange() {
+    private void handleMinimalPostProcessingAllowedSettingChange() {
         synchronized (mSyncRoot) {
-            updateSettingsLocked();
+            updateMinimalPostProcessingAllowedSettingLocked();
             scheduleTraversalLocked(false);
         }
     }
 
-    private void updateSettingsLocked() {
+    private void updateMinimalPostProcessingAllowedSettingLocked() {
         setMinimalPostProcessingAllowed(Settings.Secure.getIntForUser(
                 mContext.getContentResolver(), Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED,
                 1, UserHandle.USER_CURRENT) != 0);
     }
 
+    private void updateMirrorBuiltInDisplaySettingLocked() {
+        if (!mFlags.isDisplayContentModeManagementEnabled()) {
+            Slog.e(TAG, "MirrorBuiltInDisplay setting shouldn't be updated when the flag is off.");
+            return;
+        }
+
+        synchronized (mSyncRoot) {
+            ContentResolver resolver = mContext.getContentResolver();
+            final boolean mirrorBuiltInDisplay = Settings.Secure.getIntForUser(resolver,
+                    MIRROR_BUILT_IN_DISPLAY, 0, UserHandle.USER_CURRENT) != 0;
+            if (mMirrorBuiltInDisplay == mirrorBuiltInDisplay) {
+                return;
+            }
+            mMirrorBuiltInDisplay = mirrorBuiltInDisplay;
+        }
+    }
+
     private void restoreResolutionFromBackup() {
         int savedMode = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.SCREEN_RESOLUTION_MODE,
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 440a271..860be20 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -32,6 +32,7 @@
 import android.provider.DeviceConfigInterface;
 import android.util.IndentingPrintWriter;
 import android.util.Spline;
+import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
@@ -58,6 +59,7 @@
     private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
     private final Handler mHandler;
     private final LightSensorController mLightSensorController;
+    private int mDisplayState = Display.STATE_OFF;
 
     private final ClamperChangeListener mClamperChangeListenerExternal;
     private final Executor mExecutor;
@@ -149,16 +151,13 @@
     public DisplayBrightnessState clamp(DisplayBrightnessState displayBrightnessState,
             DisplayManagerInternal.DisplayPowerRequest request,
             float brightnessValue, boolean slowChange, int displayState) {
+        mDisplayState = displayState;
         DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(
                 displayBrightnessState);
         builder.setIsSlowChange(slowChange);
         builder.setBrightness(brightnessValue);
 
-        if (displayState != STATE_ON) {
-            mLightSensorController.stop();
-        } else {
-            adjustLightSensorSubscription();
-        }
+        adjustLightSensorSubscription();
 
         for (int i = 0; i < mModifiers.size(); i++) {
             mModifiers.get(i).apply(request, builder);
@@ -230,7 +229,8 @@
     }
 
     private void adjustLightSensorSubscription() {
-        if (mModifiers.stream().anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
+        if (mDisplayState == STATE_ON && mModifiers.stream()
+                .anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
             mLightSensorController.restart();
         } else {
             mLightSensorController.stop();
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 45106f5..585fc44 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -256,6 +256,10 @@
             Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS,
             Flags::displayListenerPerformanceImprovements
     );
+    private final FlagState mEnableDisplayContentModeManagementFlagState = new FlagState(
+            Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT,
+            Flags::enableDisplayContentModeManagement
+    );
 
     private final FlagState mSubscribeGranularDisplayEvents = new FlagState(
             Flags.FLAG_SUBSCRIBE_GRANULAR_DISPLAY_EVENTS,
@@ -556,6 +560,10 @@
         return mDisplayListenerPerformanceImprovementsFlagState.isEnabled();
     }
 
+    public boolean isDisplayContentModeManagementEnabled() {
+        return mEnableDisplayContentModeManagementFlagState.isEnabled();
+    }
+
     /**
      * @return {@code true} if the flag for subscribing to granular display events is enabled
      */
@@ -618,6 +626,7 @@
         pw.println(" " + mEnablePluginManagerFlagState);
         pw.println(" " + mDisplayListenerPerformanceImprovementsFlagState);
         pw.println(" " + mSubscribeGranularDisplayEvents);
+        pw.println(" " + mEnableDisplayContentModeManagementFlagState);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 265e453..bc44fed 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.graphics.PointF;
+import android.hardware.display.DisplayTopology;
 import android.hardware.display.DisplayViewport;
 import android.hardware.input.KeyGestureEvent;
 import android.os.IBinder;
@@ -47,6 +48,12 @@
     public abstract void setDisplayViewports(List<DisplayViewport> viewports);
 
     /**
+     * Called by {@link com.android.server.display.DisplayManagerService} to inform InputManager
+     * about changes in the displays topology.
+     */
+    public abstract void setDisplayTopology(DisplayTopology topology);
+
+    /**
      * Called by the power manager to tell the input manager whether it should start
      * watching for wake events on given displays.
      *
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 767a723..aee5e7f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -51,6 +51,7 @@
 import android.hardware.SensorPrivacyManager.Sensors;
 import android.hardware.SensorPrivacyManagerInternal;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayTopology;
 import android.hardware.display.DisplayViewport;
 import android.hardware.input.AidlInputGestureData;
 import android.hardware.input.HostUsiVersion;
@@ -660,6 +661,10 @@
         mNative.setPointerDisplayId(mWindowManagerCallbacks.getPointerDisplayId());
     }
 
+    private void setDisplayTopologyInternal(DisplayTopology topology) {
+        mNative.setDisplayTopology(topology.getGraph());
+    }
+
     /**
      * Gets the current state of a key or button by key code.
      * @param deviceId The input device id, or -1 to consult all devices.
@@ -3461,6 +3466,11 @@
         }
 
         @Override
+        public void setDisplayTopology(DisplayTopology topology) {
+            setDisplayTopologyInternal(topology);
+        }
+
+        @Override
         public void setDisplayInteractivities(SparseBooleanArray displayInteractivities) {
             boolean globallyInteractive = false;
             ArraySet<Integer> nonInteractiveDisplays = new ArraySet<>();
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 935f0ff..c72f7c0 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.display.DisplayTopologyGraph;
 import android.hardware.display.DisplayViewport;
 import android.hardware.input.InputSensorInfo;
 import android.hardware.lights.Light;
@@ -42,6 +43,8 @@
 
     void setDisplayViewports(DisplayViewport[] viewports);
 
+    void setDisplayTopology(DisplayTopologyGraph topologyGraph);
+
     int getScanCodeState(int deviceId, int sourceMask, int scanCode);
 
     int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
@@ -323,6 +326,9 @@
         public native void setDisplayViewports(DisplayViewport[] viewports);
 
         @Override
+        public native void setDisplayTopology(DisplayTopologyGraph topologyGraph);
+
+        @Override
         public native int getScanCodeState(int deviceId, int sourceMask, int scanCode);
 
         @Override
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index aa215ce..4a533f4 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -20,7 +20,6 @@
 import android.hardware.contexthub.EndpointInfo;
 import android.hardware.contexthub.HubEndpointInfo;
 import android.hardware.contexthub.HubMessage;
-import android.hardware.contexthub.HubServiceInfo;
 import android.hardware.contexthub.IContextHubEndpoint;
 import android.hardware.contexthub.IContextHubEndpointCallback;
 import android.hardware.location.IContextHubTransactionCallback;
@@ -28,6 +27,10 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashSet;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -61,6 +64,20 @@
     /** True if this endpoint is registered with the service. */
     private AtomicBoolean mIsRegistered = new AtomicBoolean(true);
 
+    private final Object mOpenSessionLock = new Object();
+
+    /** The set of session IDs that are pending remote acceptance */
+    @GuardedBy("mOpenSessionLock")
+    private final Set<Integer> mPendingSessionIds = new HashSet<>();
+
+    /** The set of session IDs that are actively enabled by this endpoint */
+    @GuardedBy("mOpenSessionLock")
+    private final Set<Integer> mActiveSessionIds = new HashSet<>();
+
+    /** The set of session IDs that are actively enabled by the remote endpoint */
+    @GuardedBy("mOpenSessionLock")
+    private final Set<Integer> mActiveRemoteSessionIds = new HashSet<>();
+
     /* package */ ContextHubEndpointBroker(
             Context context,
             IContextHubWrapper contextHubProxy,
@@ -81,25 +98,30 @@
     }
 
     @Override
-    public int openSession(HubEndpointInfo destination, HubServiceInfo serviceInfo)
+    public int openSession(HubEndpointInfo destination, String serviceDescriptor)
             throws RemoteException {
         ContextHubServiceUtil.checkPermissions(mContext);
         if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
         int sessionId = mEndpointManager.reserveSessionId();
         EndpointInfo halEndpointInfo = ContextHubServiceUtil.convertHalEndpointInfo(destination);
-        try {
-            mContextHubProxy.openEndpointSession(
-                    sessionId,
-                    halEndpointInfo.id,
-                    mHalEndpointInfo.id,
-                    serviceInfo == null ? null : serviceInfo.getServiceDescriptor());
-        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
-            Log.e(TAG, "Exception while calling HAL openEndpointSession", e);
-            mEndpointManager.returnSessionId(sessionId);
-            throw e;
-        }
 
-        return sessionId;
+        synchronized (mOpenSessionLock) {
+            try {
+                mPendingSessionIds.add(sessionId);
+                mContextHubProxy.openEndpointSession(
+                        sessionId,
+                        halEndpointInfo.id,
+                        mHalEndpointInfo.id,
+                        serviceDescriptor);
+            } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+                Log.e(TAG, "Exception while calling HAL openEndpointSession", e);
+                mPendingSessionIds.remove(sessionId);
+                mEndpointManager.returnSessionId(sessionId);
+                throw e;
+            }
+
+            return sessionId;
+        }
     }
 
     @Override
@@ -115,11 +137,6 @@
     }
 
     @Override
-    public void openSessionRequestComplete(int sessionId) {
-        // TODO(b/378487799): Implement this
-    }
-
-    @Override
     public void unregister() {
         ContextHubServiceUtil.checkPermissions(mContext);
         mIsRegistered.set(false);
@@ -128,11 +145,34 @@
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
         }
-        // TODO(b/378487799): Release reserved session IDs
+        synchronized (mOpenSessionLock) {
+            for (int id : mPendingSessionIds) {
+                mEndpointManager.returnSessionId(id);
+            }
+            for (int id : mActiveSessionIds) {
+                mEndpointManager.returnSessionId(id);
+            }
+            mPendingSessionIds.clear();
+            mActiveSessionIds.clear();
+            mActiveRemoteSessionIds.clear();
+        }
         mEndpointManager.unregisterEndpoint(mEndpointInfo.getIdentifier().getEndpoint());
     }
 
     @Override
+    public void openSessionRequestComplete(int sessionId) {
+        ContextHubServiceUtil.checkPermissions(mContext);
+        synchronized (mOpenSessionLock) {
+            try {
+                mContextHubProxy.endpointSessionOpenComplete(sessionId);
+                mActiveRemoteSessionIds.add(sessionId);
+            } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+                Log.e(TAG, "Exception while calling endpointSessionOpenComplete", e);
+            }
+        }
+    }
+
+    @Override
     public void sendMessage(
             int sessionId, HubMessage message, IContextHubTransactionCallback callback) {
         // TODO(b/381102453): Implement this
@@ -154,4 +194,53 @@
             mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
         }
     }
+
+    /* package */ void onEndpointSessionOpenRequest(
+            int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
+        if (mContextHubEndpointCallback != null) {
+            try {
+                mContextHubEndpointCallback.onSessionOpenRequest(
+                        sessionId, initiator, serviceDescriptor);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling onSessionOpenRequest", e);
+            }
+        }
+    }
+
+    /* package */ void onCloseEndpointSession(int sessionId, byte reason) {
+        synchronized (mOpenSessionLock) {
+            mPendingSessionIds.remove(sessionId);
+            mActiveSessionIds.remove(sessionId);
+            mActiveRemoteSessionIds.remove(sessionId);
+        }
+        if (mContextHubEndpointCallback != null) {
+            try {
+                mContextHubEndpointCallback.onSessionClosed(sessionId, reason);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling onSessionClosed", e);
+            }
+        }
+    }
+
+    /* package */ void onEndpointSessionOpenComplete(int sessionId) {
+        synchronized (mOpenSessionLock) {
+            mPendingSessionIds.remove(sessionId);
+            mActiveSessionIds.add(sessionId);
+        }
+        if (mContextHubEndpointCallback != null) {
+            try {
+                mContextHubEndpointCallback.onSessionOpenComplete(sessionId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling onSessionClosed", e);
+            }
+        }
+    }
+
+    /* package */ boolean hasSessionId(int sessionId) {
+        synchronized (mOpenSessionLock) {
+            return mPendingSessionIds.contains(sessionId)
+                    || mActiveSessionIds.contains(sessionId)
+                    || mActiveRemoteSessionIds.contains(sessionId);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
index fb64f99..8c5095f3 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -38,7 +38,8 @@
  *
  * @hide
  */
-/* package */ class ContextHubEndpointManager {
+/* package */ class ContextHubEndpointManager
+        implements ContextHubHalEndpointCallback.IEndpointSessionCallback {
     private static final String TAG = "ContextHubEndpointManager";
 
     /** The hub ID of the Context Hub Service. */
@@ -56,6 +57,8 @@
     /** The proxy to talk to the Context Hub. */
     private final IContextHubWrapper mContextHubProxy;
 
+    private final HubInfoRegistry mHubInfoRegistry;
+
     /** A map of endpoint IDs to brokers currently registered. */
     private final Map<Long, ContextHubEndpointBroker> mEndpointMap = new ConcurrentHashMap<>();
 
@@ -85,9 +88,11 @@
     /** Initialized to true if all initialization in the constructor succeeds. */
     private final boolean mSessionIdsValid;
 
-    /* package */ ContextHubEndpointManager(Context context, IContextHubWrapper contextHubProxy) {
+    /* package */ ContextHubEndpointManager(
+            Context context, IContextHubWrapper contextHubProxy, HubInfoRegistry hubInfoRegistry) {
         mContext = context;
         mContextHubProxy = contextHubProxy;
+        mHubInfoRegistry = hubInfoRegistry;
         int[] range = null;
         try {
             range = mContextHubProxy.requestSessionIdRange(SERVICE_SESSION_RANGE);
@@ -210,6 +215,70 @@
         mEndpointMap.remove(endpointId);
     }
 
+    @Override
+    public void onEndpointSessionOpenRequest(
+            int sessionId,
+            HubEndpointInfo.HubEndpointIdentifier destination,
+            HubEndpointInfo.HubEndpointIdentifier initiator,
+            String serviceDescriptor) {
+        if (destination.getHub() != SERVICE_HUB_ID) {
+            Log.e(
+                    TAG,
+                    "onEndpointSessionOpenRequest: invalid destination hub ID: "
+                            + destination.getHub());
+            return;
+        }
+        ContextHubEndpointBroker broker = mEndpointMap.get(destination.getEndpoint());
+        if (broker == null) {
+            Log.e(
+                    TAG,
+                    "onEndpointSessionOpenRequest: unknown destination endpoint ID: "
+                            + destination.getEndpoint());
+            return;
+        }
+        HubEndpointInfo initiatorInfo = mHubInfoRegistry.getEndpointInfo(initiator);
+        if (initiatorInfo == null) {
+            Log.e(
+                    TAG,
+                    "onEndpointSessionOpenRequest: unknown initiator endpoint ID: "
+                            + initiator.getEndpoint());
+            return;
+        }
+        broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor);
+    }
+
+    @Override
+    public void onCloseEndpointSession(int sessionId, byte reason) {
+        boolean callbackInvoked = false;
+        for (ContextHubEndpointBroker broker : mEndpointMap.values()) {
+            if (broker.hasSessionId(sessionId)) {
+                broker.onCloseEndpointSession(sessionId, reason);
+                callbackInvoked = true;
+                break;
+            }
+        }
+
+        if (!callbackInvoked) {
+            Log.w(TAG, "onCloseEndpointSession: unknown session ID " + sessionId);
+        }
+    }
+
+    @Override
+    public void onEndpointSessionOpenComplete(int sessionId) {
+        boolean callbackInvoked = false;
+        for (ContextHubEndpointBroker broker : mEndpointMap.values()) {
+            if (broker.hasSessionId(sessionId)) {
+                broker.onEndpointSessionOpenComplete(sessionId);
+                callbackInvoked = true;
+                break;
+            }
+        }
+
+        if (!callbackInvoked) {
+            Log.w(TAG, "onEndpointSessionOpenComplete: unknown session ID " + sessionId);
+        }
+    }
+
     /** @return an available endpoint ID */
     private long getNewEndpointId() {
         synchronized (mEndpointLock) {
@@ -220,6 +289,9 @@
         }
     }
 
+    /**
+     * @return true if the provided session ID range is valid
+     */
     private boolean isSessionIdRangeValid(int minId, int maxId) {
         return (minId <= maxId) && (minId >= 0) && (maxId >= 0);
     }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
index c05f7a0..8e72553 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
@@ -26,6 +26,7 @@
 public class ContextHubHalEndpointCallback
         extends android.hardware.contexthub.IEndpointCallback.Stub {
     private final IEndpointLifecycleCallback mEndpointLifecycleCallback;
+    private final IEndpointSessionCallback mEndpointSessionCallback;
 
     /** Interface for listening for endpoint start and stop events. */
     public interface IEndpointLifecycleCallback {
@@ -36,8 +37,27 @@
         void onEndpointStopped(HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason);
     }
 
-    ContextHubHalEndpointCallback(IEndpointLifecycleCallback endpointLifecycleCallback) {
+    /** Interface for listening for endpoint session events. */
+    public interface IEndpointSessionCallback {
+        /** Called when an endpoint session open is requested by the HAL. */
+        void onEndpointSessionOpenRequest(
+                int sessionId,
+                HubEndpointInfo.HubEndpointIdentifier destinationId,
+                HubEndpointInfo.HubEndpointIdentifier initiatorId,
+                String serviceDescriptor);
+
+        /** Called when a endpoint close session is completed. */
+        void onCloseEndpointSession(int sessionId, byte reason);
+
+        /** Called when a requested endpoint open session is completed */
+        void onEndpointSessionOpenComplete(int sessionId);
+    }
+
+    ContextHubHalEndpointCallback(
+            IEndpointLifecycleCallback endpointLifecycleCallback,
+            IEndpointSessionCallback endpointSessionCallback) {
         mEndpointLifecycleCallback = endpointLifecycleCallback;
+        mEndpointSessionCallback = endpointSessionCallback;
     }
 
     @Override
@@ -72,14 +92,23 @@
 
     @Override
     public void onEndpointSessionOpenRequest(
-            int i, EndpointId endpointId, EndpointId endpointId1, String s)
-            throws RemoteException {}
+            int i, EndpointId destination, EndpointId initiator, String s) throws RemoteException {
+        HubEndpointInfo.HubEndpointIdentifier destinationId =
+                new HubEndpointInfo.HubEndpointIdentifier(destination.hubId, destination.id);
+        HubEndpointInfo.HubEndpointIdentifier initiatorId =
+                new HubEndpointInfo.HubEndpointIdentifier(initiator.hubId, initiator.id);
+        mEndpointSessionCallback.onEndpointSessionOpenRequest(i, destinationId, initiatorId, s);
+    }
 
     @Override
-    public void onCloseEndpointSession(int i, byte b) throws RemoteException {}
+    public void onCloseEndpointSession(int i, byte b) throws RemoteException {
+        mEndpointSessionCallback.onCloseEndpointSession(i, b);
+    }
 
     @Override
-    public void onEndpointSessionOpenComplete(int i) throws RemoteException {}
+    public void onEndpointSessionOpenComplete(int i) throws RemoteException {
+        mEndpointSessionCallback.onEndpointSessionOpenComplete(i);
+    }
 
     @Override
     public int getInterfaceVersion() throws RemoteException {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 0d926b9..0b47a61 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -333,7 +333,8 @@
             HubInfoRegistry registry;
             try {
                 registry = new HubInfoRegistry(mContextHubWrapper);
-                mEndpointManager = new ContextHubEndpointManager(mContext, mContextHubWrapper);
+                mEndpointManager =
+                        new ContextHubEndpointManager(mContext, mContextHubWrapper, registry);
                 Log.i(TAG, "Enabling generic offload API");
             } catch (UnsupportedOperationException e) {
                 mEndpointManager = null;
@@ -533,7 +534,7 @@
         }
         try {
             mContextHubWrapper.registerEndpointCallback(
-                    new ContextHubHalEndpointCallback(mHubInfoRegistry));
+                    new ContextHubHalEndpointCallback(mHubInfoRegistry, mEndpointManager));
         } catch (RemoteException | UnsupportedOperationException e) {
             Log.e(TAG, "Exception while registering IEndpointCallback", e);
         }
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
index d2b2331..b912492 100644
--- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -87,6 +87,12 @@
         }
     }
 
+    public HubEndpointInfo getEndpointInfo(HubEndpointInfo.HubEndpointIdentifier id) {
+        synchronized (mLock) {
+            return mHubEndpointInfos.get(id);
+        }
+    }
+
     /** Invoked when HAL restarts */
     public void onHalRestart() {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index c79dc84..6cb9429 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -261,6 +261,9 @@
     public void unregisterEndpoint(android.hardware.contexthub.EndpointInfo info)
             throws RemoteException {}
 
+    /** Notifies the completion of a session opened by the HAL */
+    public void endpointSessionOpenComplete(int sessionId) throws RemoteException {}
+
     /**
      * @return True if this version of the Contexthub HAL supports Location setting notifications.
      */
@@ -745,6 +748,15 @@
             hub.unregisterEndpoint(info);
         }
 
+        @Override
+        public void endpointSessionOpenComplete(int sessionId) throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+            hub.endpointSessionOpenComplete(sessionId);
+        }
+
         public boolean supportsLocationSettingNotifications() {
             return true;
         }
diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
index 0f65d1d..2686f2b 100644
--- a/services/core/java/com/android/server/media/AudioManagerRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -46,6 +46,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.media.BluetoothRouteController.NoOpBluetoothRouteController;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -378,7 +379,12 @@
             Slog.e(
                     TAG,
                     "Could not map this selected device attribute type to an available route: "
-                            + selectedDeviceAttributesType);
+                            + selectedDeviceAttributesType
+                            + ". Available types: "
+                            + Arrays.toString(
+                                    Arrays.stream(audioDeviceInfos)
+                                            .map(AudioDeviceInfo::getType)
+                                            .toArray()));
             // We know mRouteIdToAvailableDeviceRoutes is not empty.
             newSelectedRouteHolder = mRouteIdToAvailableDeviceRoutes.values().iterator().next();
         }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index ab68ed3..abc067d 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2347,13 +2347,24 @@
             if (!Flags.enableRouteVisibilityControlApi()) {
                 return true;
             }
-            for (String permission : route.getRequiredPermissions()) {
-                if (mContext.checkPermission(permission, mPid, mUid)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    return false;
+            List<Set<String>> permissionSets = route.getRequiredPermissions();
+            if (permissionSets.isEmpty()) {
+                return true;
+            }
+            for (Set<String> permissionSet : permissionSets) {
+                boolean hasAllInSet = true;
+                for (String permission : permissionSet) {
+                    if (mContext.checkPermission(permission, mPid, mUid)
+                            != PackageManager.PERMISSION_GRANTED) {
+                        hasAllInSet = false;
+                        break;
+                    }
+                }
+                if (hasAllInSet) {
+                    return true;
                 }
             }
-            return true;
+            return false;
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index f79d9ef..65a38ae 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -123,13 +123,6 @@
 }
 
 flag {
-  name: "use_ipcdatacache_channels"
-  namespace: "systemui"
-  description: "Adds an IPCDataCache for notification channel/group lookups"
-  bug: "331677193"
-}
-
-flag {
   name: "use_ssm_user_switch_signal"
   namespace: "systemui"
   description: "This flag controls which signal is used to handle a user switch system event"
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OWNERS b/services/core/java/com/android/server/ondeviceintelligence/OWNERS
deleted file mode 100644
index 09774f7..0000000
--- a/services/core/java/com/android/server/ondeviceintelligence/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file:/core/java/android/app/ondeviceintelligence/OWNERS
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 58b1e49..be2f58d 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -138,7 +138,8 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerInternal;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerLocal;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -5851,10 +5852,10 @@
         if (isHotword) {
             return true;
         }
-        OnDeviceIntelligenceManagerInternal onDeviceIntelligenceManagerInternal =
-                mInjector.getLocalService(OnDeviceIntelligenceManagerInternal.class);
-        return onDeviceIntelligenceManagerInternal != null
-                && uid == onDeviceIntelligenceManagerInternal.getInferenceServiceUid();
+        OnDeviceIntelligenceManagerLocal onDeviceIntelligenceManagerLocal =
+                LocalManagerRegistry.getManager(OnDeviceIntelligenceManagerLocal.class);
+        return onDeviceIntelligenceManagerLocal != null
+                && uid == onDeviceIntelligenceManagerLocal.getInferenceServiceUid();
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index 4b82de0..ef49f49 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -34,6 +34,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -196,21 +197,13 @@
 
     private void doNotifyCallbacksByIntent(Intent intent, int userId,
             int[] broadcastAllowList, Handler handler) {
-        RemoteCallbackList<IRemoteCallback> callbacks;
-        synchronized (mLock) {
-            callbacks = mCallbacks;
-        }
-        doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler,
+        doNotifyCallbacks(intent, userId, broadcastAllowList, handler,
                 null /* filterExtrasFunction */);
     }
 
     private void doNotifyCallbacksByAction(String action, String pkg, Bundle extras, int[] userIds,
             SparseArray<int[]> broadcastAllowList, Handler handler,
             BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
-        RemoteCallbackList<IRemoteCallback> callbacks;
-        synchronized (mLock) {
-            callbacks = mCallbacks;
-        }
         for (int userId : userIds) {
             final Intent intent = new Intent(action,
                     pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
@@ -226,48 +219,58 @@
 
             final int[] allowUids =
                     broadcastAllowList != null ? broadcastAllowList.get(userId) : null;
-            doNotifyCallbacks(callbacks, intent, userId, allowUids, handler, filterExtrasFunction);
+            doNotifyCallbacks(intent, userId, allowUids, handler, filterExtrasFunction);
         }
     }
 
-    private void doNotifyCallbacks(RemoteCallbackList<IRemoteCallback> callbacks,
-            Intent intent, int userId, int[] allowUids, Handler handler,
+    private void doNotifyCallbacks(Intent intent, int userId, int[] allowUids, Handler handler,
             BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
-        handler.post(() -> callbacks.broadcast((callback, user) -> {
-            RegisterUser registerUser = (RegisterUser) user;
-            if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
-                    != userId)) {
-                return;
-            }
-            int registerUid = registerUser.getUid();
-            if (allowUids != null && !UserHandle.isSameApp(registerUid, Process.SYSTEM_UID)
-                    && !ArrayUtils.contains(allowUids, registerUid)) {
-                if (DEBUG) {
-                    Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + intent.getAction()
-                            + ", uid " + registerUid);
-                }
-                return;
-            }
-            Intent newIntent = intent;
-            if (filterExtrasFunction != null) {
-                final Bundle extras = intent.getExtras();
-                if (extras != null) {
-                    final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras);
-                    if (filteredExtras == null) {
-                        // caller is unable to access this intent
+        handler.post(() -> {
+            final ArrayList<Pair<IRemoteCallback, Intent>> target = new ArrayList<>();
+            synchronized (mLock) {
+                mCallbacks.broadcast((callback, user) -> {
+                    RegisterUser registerUser = (RegisterUser) user;
+                    if ((registerUser.getUserId() != UserHandle.USER_ALL)
+                            && (registerUser.getUserId() != userId)) {
+                        return;
+                    }
+                    int registerUid = registerUser.getUid();
+                    if (allowUids != null && !UserHandle.isSameApp(registerUid, Process.SYSTEM_UID)
+                            && !ArrayUtils.contains(allowUids, registerUid)) {
                         if (DEBUG) {
-                            Slog.w(TAG,
-                                    "Skip invoke PackageMonitorCallback for " + intent.getAction()
-                                            + " because null filteredExtras");
+                            Slog.w(TAG, "Skip invoke PackageMonitorCallback for "
+                                    + intent.getAction() + ", uid " + registerUid);
                         }
                         return;
                     }
-                    newIntent = new Intent(newIntent);
-                    newIntent.replaceExtras(filteredExtras);
-                }
+                    Intent newIntent = intent;
+                    if (filterExtrasFunction != null) {
+                        final Bundle extras = intent.getExtras();
+                        if (extras != null) {
+                            final Bundle filteredExtras =
+                                    filterExtrasFunction.apply(registerUid, extras);
+                            if (filteredExtras == null) {
+                                // caller is unable to access this intent
+                                if (DEBUG) {
+                                    Slog.w(TAG,
+                                            "Skip invoke PackageMonitorCallback for "
+                                                    + intent.getAction()
+                                                    + " because null filteredExtras");
+                                }
+                                return;
+                            }
+                            newIntent = new Intent(newIntent);
+                            newIntent.replaceExtras(filteredExtras);
+                        }
+                    }
+                    target.add(new Pair<>(callback, newIntent));
+                });
             }
-            invokeCallback(callback, newIntent);
-        }));
+            for (int i = 0; i < target.size(); i++) {
+                Pair<IRemoteCallback, Intent> p = target.get(i);
+                invokeCallback(p.first, p.second);
+            }
+        });
     }
 
     private void invokeCallback(IRemoteCallback callback, Intent intent) {
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 94b49e5..1fda478 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -165,6 +165,22 @@
       ]
     },
     {
+      "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases",
+      "file_patterns": [
+        "core/java/.*Install.*",
+        "services/core/.*Install.*",
+        "services/core/java/com/android/server/pm/.*"
+      ],
+      "options":[
+          {
+              "exclude-annotation":"androidx.test.filters.FlakyTest"
+          },
+          {
+              "exclude-annotation":"org.junit.Ignore"
+          }
+      ]
+    },
+    {
       "name": "CtsPackageInstallerCUJUpdateSelfTestCases",
       "file_patterns": [
         "core/java/.*Install.*",
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 63e8d99..8c588b4 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -195,23 +195,22 @@
             mLastAccumulationMonotonicHistorySize = historySize;
         }
 
-        handler.post(() -> accumulateBatteryUsageStats(stats));
+        // No need to store the accumulated stats asynchronously, as the entire accumulation
+        // operation is async
+        handler.post(() -> accumulateBatteryUsageStats(stats, false));
     }
 
     /**
      * Computes BatteryUsageStats for the period since the last accumulated stats were stored,
-     * adds them to the accumulated stats and saves the result.
+     * adds them to the accumulated stats and asynchronously saves the result.
      */
     public void accumulateBatteryUsageStats(BatteryStatsImpl stats) {
-        AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
+        accumulateBatteryUsageStats(stats, true);
+    }
 
-        final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
-                .setMaxStatsAgeMs(0)
-                .includeProcessStateData()
-                .includePowerStateData()
-                .includeScreenStateData()
-                .build();
-        updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query);
+    private void accumulateBatteryUsageStats(BatteryStatsImpl stats, boolean storeAsync) {
+        AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
+        updateAccumulatedBatteryUsageStats(accumulatedStats, stats);
 
         PowerStatsSpan powerStatsSpan = new PowerStatsSpan(AccumulatedBatteryUsageStatsSection.ID);
         powerStatsSpan.addSection(
@@ -220,8 +219,13 @@
                 accumulatedStats.startWallClockTime,
                 accumulatedStats.endMonotonicTime - accumulatedStats.startMonotonicTime);
         mMonotonicClock.write();
-        mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan,
-                accumulatedStats.builder::discard);
+        if (storeAsync) {
+            mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan,
+                    accumulatedStats.builder::discard);
+        } else {
+            mPowerStatsStore.storePowerStatsSpan(powerStatsSpan);
+            accumulatedStats.builder.discard();
+        }
     }
 
     /**
@@ -269,7 +273,7 @@
         BatteryUsageStats batteryUsageStats;
         if ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_ACCUMULATED) != 0) {
-            batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
+            batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query);
         } else if (query.getAggregatedToTimestamp() == 0) {
             BatteryUsageStats.Builder builder = computeBatteryUsageStats(stats, query,
                     query.getMonotonicStartTime(),
@@ -288,9 +292,13 @@
     }
 
     private BatteryUsageStats getAccumulatedBatteryUsageStats(BatteryStatsImpl stats,
-            BatteryUsageStatsQuery query, long currentTimeMs) {
+            BatteryUsageStatsQuery query) {
         AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
-        updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query);
+        if (accumulatedStats.endMonotonicTime == MonotonicClock.UNDEFINED
+                || mMonotonicClock.monotonicTime() - accumulatedStats.endMonotonicTime
+                > query.getMaxStatsAge()) {
+            updateAccumulatedBatteryUsageStats(accumulatedStats, stats);
+        }
         return accumulatedStats.builder.build();
     }
 
@@ -321,7 +329,7 @@
     }
 
     private void updateAccumulatedBatteryUsageStats(AccumulatedBatteryUsageStats accumulatedStats,
-            BatteryStatsImpl stats, BatteryUsageStatsQuery query) {
+            BatteryStatsImpl stats) {
         long startMonotonicTime = accumulatedStats.endMonotonicTime;
         if (startMonotonicTime == MonotonicClock.UNDEFINED) {
             startMonotonicTime = stats.getMonotonicStartTime();
@@ -333,6 +341,7 @@
             accumulatedStats.builder = new BatteryUsageStats.Builder(
                     stats.getCustomEnergyConsumerNames(), true, true, true, 0);
             accumulatedStats.startWallClockTime = stats.getStartClockTime();
+            accumulatedStats.startMonotonicTime = stats.getMonotonicStartTime();
             accumulatedStats.builder.setStatsStartTimestamp(accumulatedStats.startWallClockTime);
         }
 
@@ -342,7 +351,7 @@
         accumulatedStats.builder.setStatsDuration(endWallClockTime - startMonotonicTime);
 
         mPowerAttributor.estimatePowerConsumption(accumulatedStats.builder, stats.getHistory(),
-                startMonotonicTime, MonotonicClock.UNDEFINED);
+                startMonotonicTime, endMonotonicTime);
 
         populateGeneralInfo(accumulatedStats.builder, stats);
     }
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
index dcdd3bd..2609cf7 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
@@ -23,6 +23,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
 
 import java.util.function.Consumer;
 
@@ -169,6 +170,9 @@
                     }
                 }
             }
+            if (endTimeMs != MonotonicClock.UNDEFINED) {
+                lastTime = endTimeMs;
+            }
             if (lastTime > baseTime) {
                 mStats.setDuration(lastTime - baseTime);
                 mStats.finish(lastTime);
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e753ce8..1bed48a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -163,6 +163,7 @@
     private final RollbackPackageHealthObserver mPackageHealthObserver;
     private final AppDataRollbackHelper mAppDataRollbackHelper;
     private final Runnable mRunExpiration = this::runExpiration;
+    private final PackageWatchdog mPackageWatchdog;
 
     // The # of milli-seconds to sleep for each received ACTION_PACKAGE_ENABLE_ROLLBACK.
     // Used by #blockRollbackManager to test timeout in enabling rollbacks.
@@ -190,6 +191,7 @@
 
         mPackageHealthObserver = new RollbackPackageHealthObserver(mContext);
         mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller);
+        mPackageWatchdog = PackageWatchdog.getInstance(mContext);
 
         // Kick off and start monitoring the handler thread.
         HandlerThread handlerThread = new HandlerThread("RollbackManagerServiceHandler");
@@ -1249,12 +1251,12 @@
                 // should document in PackageInstaller.SessionParams#setEnableRollback
                 // After enabling and committing any rollback, observe packages and
                 // prepare to rollback if packages crashes too frequently.
-                mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
-                        mRollbackLifetimeDurationInMillis);
+                mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
+                        rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
             }
         } else {
-            mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
-                    mRollbackLifetimeDurationInMillis);
+            mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
+                    rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
         }
         runExpiration();
     }
@@ -1317,7 +1319,7 @@
             }
 
         });
-        PackageWatchdog.getInstance(mContext).dump(ipw);
+        mPackageWatchdog.dump(ipw);
     }
 
     @AnyThread
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
index b9c8d3d..f51c25d 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
@@ -24,9 +24,14 @@
 import android.content.Context;
 import android.os.UserManager;
 import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Slog;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** @hide */
 public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProtectionHook {
     private static final String TAG = "AdvancedProtectionDisallowCellular2G";
@@ -35,11 +40,13 @@
             new AdvancedProtectionFeature(FEATURE_ID_DISALLOW_CELLULAR_2G);
     private final DevicePolicyManager mDevicePolicyManager;
     private final TelephonyManager mTelephonyManager;
+    private final SubscriptionManager mSubscriptionManager;
 
     public DisallowCellular2GAdvancedProtectionHook(@NonNull Context context, boolean enabled) {
         super(context, enabled);
         mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
+        mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
 
         setPolicy(enabled);
     }
@@ -50,14 +57,44 @@
         return mFeature;
     }
 
+    private static boolean isEmbeddedSubscriptionVisible(SubscriptionInfo subInfo) {
+        if (subInfo.isEmbedded()
+                && (subInfo.getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING
+                        || (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()
+                                && subInfo.isOnlyNonTerrestrialNetwork()))) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private List<TelephonyManager> getActiveTelephonyManagers() {
+        List<TelephonyManager> telephonyManagers = new ArrayList<>();
+
+        for (SubscriptionInfo subInfo : mSubscriptionManager.getActiveSubscriptionInfoList()) {
+            if (isEmbeddedSubscriptionVisible(subInfo)) {
+                telephonyManagers.add(
+                        mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()));
+            }
+        }
+
+        return telephonyManagers;
+    }
+
     @Override
     public boolean isAvailable() {
-        return mTelephonyManager.isDataCapable();
+        for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
+            if (telephonyManager.isDataCapable()
+                    && telephonyManager.isRadioInterfaceCapabilitySupported(
+                            mTelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK)) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     private void setPolicy(boolean enabled) {
-        Slog.i(TAG, "setPolicy called with " + enabled);
-
         if (enabled) {
             Slog.d(TAG, "Setting DISALLOW_CELLULAR_2G_GLOBALLY restriction");
             mDevicePolicyManager.addUserRestrictionGlobally(
@@ -75,12 +112,14 @@
 
         // Leave 2G disabled even if APM is disabled.
         if (!enabled) {
-            long oldAllowedTypes =
-                    mTelephonyManager.getAllowedNetworkTypesForReason(
-                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
-            long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
-            mTelephonyManager.setAllowedNetworkTypesForReason(
-                    TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+            for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
+                long oldAllowedTypes =
+                        telephonyManager.getAllowedNetworkTypesForReason(
+                                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+                long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+                telephonyManager.setAllowedNetworkTypesForReason(
+                        TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
index 0ea88e8..687442b 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
@@ -28,6 +28,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public class DataAggregator {
     private static final String TAG = "IntrusionDetection DataAggregator";
@@ -36,11 +37,10 @@
     private static final int MSG_DISABLE = 2;
 
     private static final int STORED_EVENTS_SIZE_LIMIT = 1024;
-    private static final IntrusionDetectionAdminReceiver ADMIN_RECEIVER =
-            new IntrusionDetectionAdminReceiver();
 
     private final IntrusionDetectionService mIntrusionDetectionService;
     private final ArrayList<DataSource> mDataSources;
+    private final AtomicBoolean mIsLoggingInitialized = new AtomicBoolean(false);
 
     private Context mContext;
     private List<IntrusionDetectionEvent> mStoredEvents = new ArrayList<>();
@@ -59,30 +59,20 @@
         mHandler = new EventHandler(looper, this);
     }
 
-    /**
-     * Initialize DataSources
-     * @return Whether the initialization succeeds.
-     */
-    public boolean initialize() {
-        SecurityLogSource securityLogSource = new SecurityLogSource(mContext, this);
-        mDataSources.add(securityLogSource);
-
-        NetworkLogSource networkLogSource = new NetworkLogSource(mContext, this);
-        ADMIN_RECEIVER.setNetworkLogEventCallback(networkLogSource);
-        mDataSources.add(networkLogSource);
-
-        for (DataSource ds : mDataSources) {
-            if (!ds.initialize()) {
-                return false;
-            }
-        }
-        return true;
+    /** Initialize DataSources */
+    private void initialize() {
+        mDataSources.add(new SecurityLogSource(mContext, this));
+        mDataSources.add(new NetworkLogSource(mContext, this));
     }
 
     /**
      * Enable the data collection of all DataSources.
      */
     public void enable() {
+        if (!mIsLoggingInitialized.get()) {
+            initialize();
+            mIsLoggingInitialized.set(true);
+        }
         mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND,
                 /* allowIo */ false);
         mHandlerThread.start();
@@ -111,9 +101,6 @@
      */
     public void disable() {
         mHandler.obtainMessage(MSG_DISABLE).sendToTarget();
-        for (DataSource ds : mDataSources) {
-            ds.disable();
-        }
     }
 
     private void onNewSingleData(IntrusionDetectionEvent event) {
diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
index 61fac46..0bc4482 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
@@ -18,11 +18,6 @@
 
 public interface DataSource {
     /**
-     * Initialize the data source.
-     */
-    boolean initialize();
-
-    /**
      * Enable the data collection.
      */
     void enable();
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java
deleted file mode 100644
index dba7374..0000000
--- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java
+++ /dev/null
@@ -1,42 +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.server.security.intrusiondetection;
-
-import android.app.admin.DeviceAdminReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Slog;
-
-public class IntrusionDetectionAdminReceiver extends DeviceAdminReceiver {
-    private static final String TAG = "IntrusionDetectionAdminReceiver";
-
-    private static NetworkLogSource sNetworkLogSource;
-
-    @Override
-    public void onNetworkLogsAvailable(
-            Context context, Intent intent, long batchToken, int networkLogsCount) {
-        if (sNetworkLogSource != null) {
-            sNetworkLogSource.onNetworkLogsAvailable(batchToken);
-        } else {
-            Slog.w(TAG, "Network log receiver is not initialized");
-        }
-    }
-
-    public void setNetworkLogEventCallback(NetworkLogSource networkLogSource) {
-        sNetworkLogSource = networkLogSource;
-    }
-}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
index b25656e..a16e66d 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
@@ -24,42 +24,56 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.security.intrusiondetection.IIntrusionDetectionEventTransport;
 import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.intrusiondetection.IIntrusionDetectionEventTransport;
 import android.text.TextUtils;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.infra.AndroidFuture;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.Process;
 import java.util.List;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
 
 public class IntrusionDetectionEventTransportConnection implements ServiceConnection {
+    private static final String PRODUCTION_BUILD = "user";
+    private static final String PROPERTY_BUILD_TYPE = "ro.build.type";
+    private static final String PROPERTY_INTRUSION_DETECTION_SERVICE_NAME =
+            "debug.intrusiondetection_package_name";
+    private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 min
     private static final String TAG = "IntrusionDetectionEventTransportConnection";
-    private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 mins
     private final Context mContext;
     private String mIntrusionDetectionEventTransportConfig;
     volatile IIntrusionDetectionEventTransport mService;
 
+
     public IntrusionDetectionEventTransportConnection(Context context) {
         mContext = context;
-        mService = null;
     }
 
     /**
      * Initialize the IntrusionDetectionEventTransport binder service.
-     * @return Whether the initialization succeed.
+     *
+     * @return Whether the initialization succeeds.
      */
     public boolean initialize() {
+        Slog.d(TAG, "initialize");
         if (!bindService()) {
             return false;
         }
+        // Wait for the service to be connected before calling initialize.
+        waitForConnection();
         AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
         try {
             mService.initialize(resultFuture);
@@ -77,6 +91,20 @@
         }
     }
 
+    private void waitForConnection() {
+        synchronized (this) {
+            while (mService == null) {
+                Slog.d(TAG, "waiting for connection to service...");
+                try {
+                    this.wait();
+                } catch (InterruptedException e) {
+                    /* never interrupted */
+                }
+            }
+            Slog.d(TAG, "connected to service");
+        }
+    }
+
     /**
      * Add data to the IntrusionDetectionEventTransport binder service.
      * @param data List of IntrusionDetectionEvent.
@@ -118,11 +146,42 @@
         }
     }
 
+    private String getSystemPropertyValue(String propertyName) {
+        String commandString = "getprop " + propertyName;
+        try {
+            Process process = Runtime.getRuntime().exec(commandString);
+            BufferedReader reader =
+                    new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String propertyValue = reader.readLine();
+            reader.close();
+            return propertyValue;
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to get system property value:", e);
+            return null;
+        }
+    }
+
     private boolean bindService() {
-        mIntrusionDetectionEventTransportConfig = mContext.getString(
-                com.android.internal.R.string.config_intrusionDetectionEventTransport);
+        String buildType = getSystemPropertyValue(PROPERTY_BUILD_TYPE);
+        mIntrusionDetectionEventTransportConfig =
+                mContext.getString(
+                        com.android.internal.R.string.config_intrusionDetectionEventTransport);
+
+        // If the build type is not production, and a property value is set, use it instead.
+        // This allows us to test the service with a different config.
+        if (!buildType.equals(PRODUCTION_BUILD)
+                && !TextUtils.isEmpty(
+                        getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME))) {
+            mIntrusionDetectionEventTransportConfig =
+                    getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME);
+        }
+        Slog.d(
+                TAG,
+                "mIntrusionDetectionEventTransportConfig: "
+                        + mIntrusionDetectionEventTransportConfig);
+
         if (TextUtils.isEmpty(mIntrusionDetectionEventTransportConfig)) {
-            Slog.e(TAG, "config_intrusionDetectionEventTransport is empty");
+            Slog.e(TAG, "Unable to find a valid config for the transport service");
             return false;
         }
 
@@ -163,11 +222,19 @@
 
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
-        mService = IIntrusionDetectionEventTransport.Stub.asInterface(service);
+        synchronized (this) {
+            mService = IIntrusionDetectionEventTransport.Stub.asInterface(service);
+            Slog.d(TAG, "connected to service");
+            this.notifyAll();
+        }
     }
 
     @Override
     public void onServiceDisconnected(ComponentName name) {
-        mService = null;
+        synchronized (this) {
+            mService = null;
+            Slog.d(TAG, "disconnected from service");
+            this.notifyAll();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java
index 0287b41..8ff1c7f 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java
@@ -232,12 +232,10 @@
             return;
         }
 
-        // TODO: temporarily disable the following for the CTS IntrusionDetectionManagerTest.
-        //  Enable it when the transport component is ready.
-        // if (!mIntrusionDetectionEventTransportConnection.initialize()) {
-        //     callback.onFailure(ERROR_TRANSPORT_UNAVAILABLE);
-        //   return;
-        // }
+        if (!mIntrusionDetectionEventTransportConnection.initialize()) {
+            callback.onFailure(ERROR_TRANSPORT_UNAVAILABLE);
+            return;
+        }
 
         mDataAggregator.enable();
         mState = STATE_ENABLED;
@@ -252,9 +250,7 @@
             return;
         }
 
-        // TODO: temporarily disable the following for the CTS IntrusionDetectionManagerTest.
-        //  Enable it when the transport component is ready.
-        // mIntrusionDetectionEventTransportConnection.release();
+        mIntrusionDetectionEventTransportConnection.release();
         mDataAggregator.disable();
         mState = STATE_DISABLED;
         notifyStateMonitors();
diff --git a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
index 1c93d3f..083b1fd 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
@@ -17,118 +17,131 @@
 package com.android.server.security.intrusiondetection;
 
 import android.app.admin.ConnectEvent;
-import android.app.admin.DevicePolicyManager;
 import android.app.admin.DnsEvent;
-import android.app.admin.NetworkEvent;
-import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.net.metrics.IpConnectivityLog;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.security.intrusiondetection.IntrusionDetectionEvent;
 import android.util.Slog;
 
-import java.util.List;
-import java.util.stream.Collectors;
+import com.android.server.LocalServices;
+import com.android.server.net.BaseNetdEventCallback;
+
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public class NetworkLogSource implements DataSource {
 
     private static final String TAG = "IntrusionDetectionEvent NetworkLogSource";
+    private final AtomicBoolean mIsNetworkLoggingEnabled = new AtomicBoolean(false);
+    private final PackageManagerInternal mPm;
 
-    private DevicePolicyManager mDpm;
-    private ComponentName mAdmin;
     private DataAggregator mDataAggregator;
 
-    public NetworkLogSource(Context context, DataAggregator dataAggregator) {
+    private IIpConnectivityMetrics mIpConnectivityMetrics;
+    private long mId;
+
+    public NetworkLogSource(Context context, DataAggregator dataAggregator)
+            throws SecurityException {
         mDataAggregator = dataAggregator;
-        mDpm = context.getSystemService(DevicePolicyManager.class);
-        mAdmin = new ComponentName(context, IntrusionDetectionAdminReceiver.class);
+        mPm = LocalServices.getService(PackageManagerInternal.class);
+        mId = 0;
+        initIpConnectivityMetrics();
     }
 
-    @Override
-    public boolean initialize() {
-        try {
-            if (!mDpm.isAdminActive(mAdmin)) {
-                Slog.e(TAG, "Admin " + mAdmin.flattenToString() + "is not active admin");
-                return false;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "Security exception in initialize: ", e);
-            return false;
-        }
-        return true;
+    private void initIpConnectivityMetrics() {
+        mIpConnectivityMetrics =
+                (IIpConnectivityMetrics)
+                        IIpConnectivityMetrics.Stub.asInterface(
+                                ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
     }
 
     @Override
     public void enable() {
-        enableNetworkLog();
+        if (mIsNetworkLoggingEnabled.get()) {
+            Slog.w(TAG, "Network logging is already enabled");
+            return;
+        }
+        try {
+            if (mIpConnectivityMetrics.addNetdEventCallback(
+                    INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY, mNetdEventCallback)) {
+                mIsNetworkLoggingEnabled.set(true);
+            } else {
+                Slog.e(TAG, "Failed to enable network logging; invalid callback");
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to enable network logging; ", e);
+        }
     }
 
     @Override
     public void disable() {
-        disableNetworkLog();
-    }
-
-    private void enableNetworkLog() {
-        if (!isNetworkLogEnabled()) {
-            mDpm.setNetworkLoggingEnabled(mAdmin, true);
+        if (!mIsNetworkLoggingEnabled.get()) {
+            Slog.w(TAG, "Network logging is already disabled");
+            return;
         }
-    }
-
-    private void disableNetworkLog() {
-        if (isNetworkLogEnabled()) {
-            mDpm.setNetworkLoggingEnabled(mAdmin, false);
-        }
-    }
-
-    private boolean isNetworkLogEnabled() {
-        return mDpm.isNetworkLoggingEnabled(mAdmin);
-    }
-
-    /**
-     * Retrieve network logs when onNetworkLogsAvailable callback is received.
-     *
-     * @param batchToken The token representing the current batch of network logs.
-     */
-    public void onNetworkLogsAvailable(long batchToken) {
-        List<NetworkEvent> events;
         try {
-            events = mDpm.retrieveNetworkLogs(mAdmin, batchToken);
-        } catch (SecurityException e) {
-            Slog.e(
-                    TAG,
-                    "Admin "
-                            + mAdmin.flattenToString()
-                            + "does not have permission to retrieve network logs",
-                    e);
-            return;
-        }
-        if (events == null) {
-            if (!isNetworkLogEnabled()) {
-                Slog.w(TAG, "Network logging is disabled");
+            if (!mIpConnectivityMetrics.removeNetdEventCallback(
+                    INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST)) {
+
+                mIsNetworkLoggingEnabled.set(false);
             } else {
-                Slog.e(TAG, "Invalid batch token: " + batchToken);
+                Slog.e(TAG, "Failed to enable network logging; invalid callback");
             }
-            return;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to disable network logging; ", e);
         }
-
-        List<IntrusionDetectionEvent> intrusionDetectionEvents =
-                events.stream()
-                        .filter(event -> event != null)
-                        .map(event -> toIntrusionDetectionEvent(event))
-                        .collect(Collectors.toList());
-        mDataAggregator.addBatchData(intrusionDetectionEvents);
     }
 
-    private IntrusionDetectionEvent toIntrusionDetectionEvent(NetworkEvent event) {
-        if (event instanceof DnsEvent) {
-            DnsEvent dnsEvent = (DnsEvent) event;
-            return new IntrusionDetectionEvent(dnsEvent);
-        } else if (event instanceof ConnectEvent) {
-            ConnectEvent connectEvent = (ConnectEvent) event;
-            return new IntrusionDetectionEvent(connectEvent);
+    private void incrementEventID() {
+        if (mId == Long.MAX_VALUE) {
+            Slog.i(TAG, "Reached maximum id value; wrapping around.");
+            mId = 0;
+        } else {
+            mId++;
         }
-        throw new IllegalArgumentException(
-                "Invalid event type with ID: "
-                        + event.getId()
-                        + "from package: "
-                        + event.getPackageName());
     }
+
+    private final INetdEventCallback mNetdEventCallback =
+            new BaseNetdEventCallback() {
+                @Override
+                public void onDnsEvent(
+                        int netId,
+                        int eventType,
+                        int returnCode,
+                        String hostname,
+                        String[] ipAddresses,
+                        int ipAddressesCount,
+                        long timestamp,
+                        int uid) {
+                    if (!mIsNetworkLoggingEnabled.get()) {
+                        return;
+                    }
+                    DnsEvent dnsEvent =
+                            new DnsEvent(
+                                    hostname,
+                                    ipAddresses,
+                                    ipAddressesCount,
+                                    mPm.getNameForUid(uid),
+                                    timestamp);
+                    dnsEvent.setId(mId);
+                    incrementEventID();
+                    mDataAggregator.addSingleData(new IntrusionDetectionEvent(dnsEvent));
+                }
+
+                @Override
+                public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) {
+                    if (!mIsNetworkLoggingEnabled.get()) {
+                        return;
+                    }
+                    ConnectEvent connectEvent =
+                            new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid), timestamp);
+                    connectEvent.setId(mId);
+                    incrementEventID();
+                    mDataAggregator.addSingleData(new IntrusionDetectionEvent(connectEvent));
+                }
+            };
 }
diff --git a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
index c5f736e..5611905 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
@@ -43,26 +43,9 @@
         mDataAggregator = dataAggregator;
         mDpm = context.getSystemService(DevicePolicyManager.class);
         mExecutor = Executors.newSingleThreadExecutor();
-    }
-
-    @Override
-    public boolean initialize() {
-        // Confirm caller is system and the device is managed. Otherwise logs will
-        // be redacted.
-        try {
-            if (!mDpm.isDeviceManaged()) {
-                Slog.e(TAG, "Caller does not have device owner permissions");
-                return false;
-            }
-        } catch (SecurityException e) {
-            Slog.e(TAG, "Security exception in initialize: ", e);
-            return false;
-        }
         mEventCallback = new SecurityEventCallback();
-        return true;
     }
 
-
     @Override
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void enable() {
@@ -99,6 +82,10 @@
 
         @Override
         public void accept(List<SecurityEvent> events) {
+            if (events.size() == 0) {
+                Slog.w(TAG, "No events received; caller may not be authorized");
+                return;
+            }
             List<IntrusionDetectionEvent> intrusionDetectionEvents =
                     events.stream()
                             .filter(event -> event != null)
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 908f51b..f8877ad 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -129,6 +129,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -339,7 +341,11 @@
     }
 
     @Override
-    public void onDisplayAdded(int displayId) {}
+    public void onDisplayAdded(int displayId) {
+        synchronized (mLock) {
+            mDisplayUiState.put(displayId, new UiState());
+        }
+    }
 
     @Override
     public void onDisplayRemoved(int displayId) {
@@ -1710,8 +1716,6 @@
             icons = new ArrayMap<>(mIcons);
         }
         synchronized (mLock) {
-            // TODO(b/118592525): Currently, status bar only works on the default display.
-            // Make it aware of multi-display if needed.
             final UiState state = mDisplayUiState.get(DEFAULT_DISPLAY);
             return new RegisterStatusBarResult(icons, gatherDisableActionsLocked(mCurrentUserId, 1),
                     state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
@@ -1722,6 +1726,46 @@
         }
     }
 
+    @Override
+    public Map<String, RegisterStatusBarResult> registerStatusBarForAllDisplays(IStatusBar bar) {
+        enforceStatusBarService();
+        enforceValidCallingUser();
+
+        Slog.i(TAG, "registerStatusBarForAllDisplays bar=" + bar);
+        mBar = bar;
+        mDeathRecipient.linkToDeath();
+        notifyBarAttachChanged();
+
+        synchronized (mLock) {
+            Map<String, RegisterStatusBarResult> results = new HashMap<>();
+
+            for (int i = 0; i < mDisplayUiState.size(); i++) {
+                final int displayId = mDisplayUiState.keyAt(i);
+                final UiState state = mDisplayUiState.get(displayId);
+
+                final ArrayMap<String, StatusBarIcon> icons;
+                synchronized (mIcons) {
+                    icons = new ArrayMap<>(mIcons);
+                }
+
+                if (state != null) {
+                    results.put(String.valueOf(displayId),
+                            new RegisterStatusBarResult(icons,
+                                    gatherDisableActionsLocked(mCurrentUserId, 1),
+                                    state.mAppearance, state.mAppearanceRegions,
+                                    state.mImeWindowVis,
+                                    state.mImeBackDisposition, state.mShowImeSwitcher,
+                                    gatherDisableActionsLocked(mCurrentUserId, 2),
+                                    state.mNavbarColorManagedByIme, state.mBehavior,
+                                    state.mRequestedVisibleTypes,
+                                    state.mPackageName, state.mTransientBarTypes,
+                                    state.mLetterboxDetails));
+                }
+            }
+            return results;
+        }
+    }
+
     private void notifyBarAttachChanged() {
         UiThread.getHandler().post(() -> {
             if (mGlobalActionListener == null) return;
diff --git a/services/core/java/com/android/server/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
index 63a3e5a..a38fc5b 100644
--- a/services/core/java/com/android/server/telecom/TelecomLoaderService.java
+++ b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManagerInternal;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -63,7 +64,10 @@
             // as this loader (process="system") that's redundant here.
             try {
                 ITelecomLoader telecomLoader = ITelecomLoader.Stub.asInterface(service);
-                ITelecomService telecomService = telecomLoader.createTelecomService(mServiceRepo);
+                PackageManagerInternal packageManagerInternal =
+                        LocalServices.getService(PackageManagerInternal.class);
+                ITelecomService telecomService = telecomLoader.createTelecomService(mServiceRepo,
+                        packageManagerInternal.getSystemUiServiceComponent().getPackageName());
 
                 SmsApplication.getDefaultMmsApplication(mContext, false);
                 ServiceManager.addService(Context.TELECOM_SERVICE, telecomService.asBinder());
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 93ccd74..6ccceb9 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -243,6 +243,19 @@
         }
     }
 
+    /** Called when the surface of display is changed to a different instance. */
+    void resetRecordingDisplay(int displayId) {
+        if (!isCurrentlyRecording()
+                || mContentRecordingSession.getDisplayToRecord() != displayId) {
+            return;
+        }
+        ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                "Content Recording: Display %d changed surface so stop recording", displayId);
+        mDisplayContent.mWmService.mTransactionFactory.get().remove(mRecordedSurface).apply();
+        mRecordedSurface = null;
+        // Do not un-set the token, in case new surface is ready and recording should begin again.
+    }
+
     /**
      * Pauses recording on this display content. Note the session does not need to be updated,
      * since recording can be resumed still.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1f224e2..0b66158 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1272,7 +1272,13 @@
     @Override
     void migrateToNewSurfaceControl(Transaction t) {
         t.remove(mSurfaceControl);
-
+        // Reset the recording displays which were mirroring this display.
+        for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
+            final ContentRecorder recorder = mRootWindowContainer.getChildAt(i).mContentRecorder;
+            if (recorder != null) {
+                recorder.resetRecordingDisplay(mDisplayId);
+            }
+        }
         mLastSurfacePosition.set(0, 0);
         mLastDeltaRotation = Surface.ROTATION_0;
 
@@ -7113,9 +7119,11 @@
         /**
          * @see #getRequestedVisibleTypes()
          */
-        void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
-            if (mRequestedVisibleTypes != requestedVisibleTypes) {
-                mRequestedVisibleTypes = requestedVisibleTypes;
+        void updateRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
+            int newRequestedVisibleTypes =
+                    (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
+            if (mRequestedVisibleTypes != newRequestedVisibleTypes) {
+                mRequestedVisibleTypes = newRequestedVisibleTypes;
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 2a5a3a5..1c4e487 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -528,7 +528,7 @@
             }
             // Only allow the extras to be dispatched to a global-intercepting drag target
             ClipData data = null;
-            if (interceptsGlobalDrag) {
+            if (interceptsGlobalDrag && mData != null) {
                 data = mData.copyForTransferWithActivityInfo();
                 PersistableBundle extras = data.getDescription().getExtras() != null
                         ? data.getDescription().getExtras()
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 27f82d9..7fdc2c6 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -40,7 +40,7 @@
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
-import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
+import static com.android.launcher3.Flags.enableUseTopVisibleActivityForExcludeFromRecentTask;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS;
@@ -1529,7 +1529,7 @@
                 // The Recents is only supported on default display now, we should only keep the
                 // most recent task of home display.
                 boolean isMostRecentTask;
-                if (enableRefactorTaskThumbnail()) {
+                if (enableUseTopVisibleActivityForExcludeFromRecentTask()) {
                     isMostRecentTask = task.getTopVisibleActivity() != null;
                 } else {
                     isMostRecentTask = taskIndex == 0;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f090ef1..4ed1206 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3425,6 +3425,7 @@
         info.isTopActivityNoDisplay = top != null && top.isNoDisplay();
         info.isSleeping = shouldSleepActivities();
         info.isTopActivityTransparent = top != null && !top.fillsParent();
+        info.isActivityStackTransparent = !topTask.forAllActivities(r -> (r.occludesParent()));
         info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
         final WindowState windowState = top != null
                 ? top.findMainWindow(/* includeStartingApp= */ false) : null;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 8562bb2..f3c03cb 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -791,19 +791,30 @@
             ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Requesting StartTransition: %s", transition);
             ActivityManager.RunningTaskInfo startTaskInfo = null;
-            ActivityManager.RunningTaskInfo pipTaskInfo = null;
+            TransitionRequestInfo.PipChange pipChange = null;
             if (startTask != null) {
                 startTaskInfo = startTask.getTaskInfo();
             }
 
             // set the pip task in the request if provided
             if (transition.getPipActivity() != null) {
-                pipTaskInfo = transition.getPipActivity().getTask().getTaskInfo();
+                ActivityManager.RunningTaskInfo pipTaskInfo =
+                        transition.getPipActivity().getTask().getTaskInfo();
+                ActivityRecord pipActivity = transition.getPipActivity();
+                if (pipActivity.getTaskFragment() != null
+                        && pipActivity.getTaskFragment() != pipActivity.getTask()) {
+                    // If the PiP activity is in a TF different from its task, this could be
+                    // AE-to-PiP case, so PipChange will have the TF token cached separately.
+                    pipChange = new TransitionRequestInfo.PipChange(pipActivity.getTaskFragment()
+                            .mRemoteToken.toWindowContainerToken(), pipTaskInfo);
+                } else {
+                    pipChange = new TransitionRequestInfo.PipChange(pipTaskInfo);
+                }
                 transition.setPipActivity(null);
             }
 
             final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
-                    startTaskInfo, pipTaskInfo, remoteTransition, displayChange,
+                    startTaskInfo, pipChange, remoteTransition, displayChange,
                     transition.getFlags(), transition.getSyncId());
 
             transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 00ade80..b42ce64f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4671,7 +4671,8 @@
     @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public void updateDisplayWindowRequestedVisibleTypes(int displayId,
-            @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token statsToken) {
+            @InsetsType int visibleTypes, @InsetsType int mask,
+            @Nullable ImeTracker.Token statsToken) {
         updateDisplayWindowRequestedVisibleTypes_enforcePermission();
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -4684,7 +4685,7 @@
                 }
                 ImeTracker.forLogging().onProgress(statsToken,
                         ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES);
-                dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes);
+                dc.mRemoteInsetsControlTarget.updateRequestedVisibleTypes(visibleTypes, mask);
                 // TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
                 //  IME provider. Check if we have to create a new request here, if null.
                 dc.getInsetsStateController().onRequestedVisibleTypesChanged(
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index ddff24d..66921ff 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1335,11 +1335,11 @@
             }
             case HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK: {
                 final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
-                Task pipTask = container.asTask();
-                if (pipTask == null) {
+                TaskFragment pipTaskFragment = container.asTaskFragment();
+                if (pipTaskFragment == null) {
                     break;
                 }
-                ActivityRecord pipActivity = pipTask.getActivity(
+                ActivityRecord pipActivity = pipTaskFragment.getActivity(
                         (activity) -> activity.pictureInPictureArgs != null);
 
                 if (pipActivity.isState(RESUMED)) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 5a45730..0464230 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -68,6 +68,7 @@
 #include <map>
 #include <vector>
 
+#include "android_hardware_display_DisplayTopology.h"
 #include "android_hardware_display_DisplayViewport.h"
 #include "android_hardware_input_InputApplicationHandle.h"
 #include "android_hardware_input_InputWindowHandle.h"
@@ -321,6 +322,8 @@
 
     void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
 
+    void setDisplayTopology(JNIEnv* env, jobject topologyGraph);
+
     base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
     base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
                                                                    const std::string& name,
@@ -640,6 +643,11 @@
             InputReaderConfiguration::Change::DISPLAY_INFO);
 }
 
+void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph) {
+    android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph);
+    // TODO(b/367661489): Use the topology
+}
+
 base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
         const std::string& name) {
     ATRACE_CALL();
@@ -2092,6 +2100,12 @@
     im->setDisplayViewports(env, viewportObjArray);
 }
 
+static void nativeSetDisplayTopology(JNIEnv* env, jobject nativeImplObj,
+                                     jobject displayTopologyObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    im->setDisplayTopology(env, displayTopologyObj);
+}
+
 static jint nativeGetScanCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                    jint sourceMask, jint scanCode) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3148,6 +3162,8 @@
         {"start", "()V", (void*)nativeStart},
         {"setDisplayViewports", "([Landroid/hardware/display/DisplayViewport;)V",
          (void*)nativeSetDisplayViewports},
+        {"setDisplayTopology", "(Landroid/hardware/display/DisplayTopologyGraph;)V",
+         (void*)nativeSetDisplayTopology},
         {"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
         {"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
         {"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index df37ec3..09fd8d4 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -49,6 +49,7 @@
 int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
 int register_android_server_SyntheticPasswordManager(JNIEnv* env);
 int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_hardware_display_DisplayTopology(JNIEnv* env);
 int register_android_server_am_OomConnection(JNIEnv* env);
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
 int register_android_server_am_Freezer(JNIEnv* env);
@@ -114,6 +115,7 @@
     register_android_server_storage_AppFuse(env);
     register_android_server_SyntheticPasswordManager(env);
     register_android_hardware_display_DisplayViewport(env);
+    register_android_hardware_display_DisplayTopology(env);
     register_android_server_am_OomConnection(env);
     register_android_server_am_CachedAppOptimizer(env);
     register_android_server_am_Freezer(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index e4db4bd..543e32f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -672,10 +672,6 @@
         }
     }
 
-    void saveToXml(TypedXmlSerializer serializer) throws IOException {
-        mPolicyKey.saveToXml(serializer);
-    }
-
     @Nullable
     static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser)
             throws XmlPullParserException, IOException {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index a4fa089..0d9dbaa 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.PolicyValue;
-import android.app.admin.flags.Flags;
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.util.XmlUtils;
@@ -41,7 +40,6 @@
     private static final String TAG = "PolicyState";
     private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
 
-    private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry";
     private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry";
     private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
     private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry";
@@ -225,12 +223,6 @@
     }
 
     void saveToXml(TypedXmlSerializer serializer) throws IOException {
-        if (!Flags.dontWritePolicyDefinition()) {
-            serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
-            mPolicyDefinition.saveToXml(serializer);
-            serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
-        }
-
         if (mCurrentResolvedPolicy != null) {
             serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
             mPolicyDefinition.savePolicyValueToXml(
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index aa63c4a..65315af 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -201,7 +201,6 @@
 import com.android.server.notification.NotificationManagerService;
 import com.android.server.oemlock.OemLockService;
 import com.android.server.om.OverlayManagerService;
-import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService;
 import com.android.server.os.BugreportManagerService;
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.NativeTombstoneManagerService;
@@ -392,6 +391,8 @@
             "com.android.server.sdksandbox.SdkSandboxManagerService$Lifecycle";
     private static final String AD_SERVICES_MANAGER_SERVICE_CLASS =
             "com.android.server.adservices.AdServicesManagerService$Lifecycle";
+    private static final String ON_DEVICE_INTELLIGENCE_MANAGER_SERVICE_CLASS =
+            "com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService";
     private static final String ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS =
             "com.android.server.ondevicepersonalization."
                     + "OnDevicePersonalizationSystemService$Lifecycle";
@@ -3453,7 +3454,7 @@
 
     private void startOnDeviceIntelligenceService(TimingsTraceAndSlog t) {
         t.traceBegin("startOnDeviceIntelligenceManagerService");
-        mSystemServiceManager.startService(OnDeviceIntelligenceManagerService.class);
+        mSystemServiceManager.startService(ON_DEVICE_INTELLIGENCE_MANAGER_SERVICE_CLASS);
         t.traceEnd();
     }
 
diff --git a/services/proguard.flags b/services/proguard.flags
index 977bd19..0e1f68e 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -44,6 +44,9 @@
 -keep,allowoptimization,allowaccessmodification class com.android.server.input.NativeInputManagerService$NativeImpl { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.ThreadPriorityBooster { *; }
 
+# allow invoking start-service using class name in both apex and services jar.
+-keep,allowoptimization,allowaccessmodification class com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService { *; }
+
 # Keep all aconfig Flag class as they might be statically referenced by other packages
 # An merge or inlining could lead to missing dependencies that cause run time errors
 -keepclassmembernames class android.**.Flags, com.android.**.Flags { public *; }
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 3093c42..0ccaa60 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -29,11 +29,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.UserHandle;
 import android.util.SparseArray;
 
 import com.android.internal.R;
@@ -63,11 +65,13 @@
     private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>();
 
     private final DevicePolicyManagerInternal mDpmInternal;
+    private final PackageManager mPackageManager;
     private final UserManagerInternal mUserManagerInternal;
 
     public SupervisionService(Context context) {
         mContext = context.createAttributionContext(LOG_TAG);
         mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
+        mPackageManager = context.getPackageManager();
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
         mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
     }
@@ -148,12 +152,13 @@
     /** Returns whether the supervision app has profile owner status. */
     private boolean isProfileOwner(@UserIdInt int userId) {
         ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(userId);
-        if (profileOwner == null) {
-            return false;
-        }
+        return profileOwner != null && isSupervisionAppPackage(profileOwner.getPackageName());
+    }
 
-        String configPackage = mContext.getResources().getString(R.string.config_systemSupervision);
-        return profileOwner.getPackageName().equals(configPackage);
+    /** Returns whether the given package name belongs to the supervision role holder. */
+    private boolean isSupervisionAppPackage(String packageName) {
+        return packageName.equals(
+                mContext.getResources().getString(R.string.config_systemSupervision));
     }
 
     public static class Lifecycle extends SystemService {
@@ -211,6 +216,21 @@
 
     private final class SupervisionManagerInternalImpl extends SupervisionManagerInternal {
         @Override
+        public boolean isActiveSupervisionApp(int uid) {
+            String[] packages = mPackageManager.getPackagesForUid(uid);
+            if (packages == null) {
+                return false;
+            }
+            for (var packageName : packages) {
+                if (SupervisionService.this.isSupervisionAppPackage(packageName)) {
+                    int userId = UserHandle.getUserId(uid);
+                    return SupervisionService.this.isSupervisionEnabledForUser(userId);
+                }
+            }
+            return false;
+        }
+
+        @Override
         public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
             return SupervisionService.this.isSupervisionEnabledForUser(userId);
         }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 66e9c98..238654d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -126,16 +127,11 @@
 
     @Test
     public void testConstructor_doesNotStartsLightSensorController() {
-        verify(mMockLightSensorController, never()).restart();
-    }
-
-    @Test
-    public void testConstructor_startsLightSensorController() {
         when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
 
         mClamperController = createBrightnessClamperController();
 
-        verify(mMockLightSensorController).restart();
+        verify(mMockLightSensorController, never()).restart();
     }
 
     @Test
@@ -171,20 +167,43 @@
 
     @Test
     public void testOnDisplayChanged_doesNotRestartLightSensor() {
+        mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+                false, STATE_ON);
+        reset(mMockLightSensorController);
+
         mClamperController.onDisplayChanged(mMockDisplayDeviceData);
 
         verify(mMockLightSensorController, never()).restart();
+        verify(mMockLightSensorController).stop();
     }
 
     @Test
     public void testOnDisplayChanged_restartsLightSensor() {
         when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+        mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+                false, STATE_ON);
+        reset(mMockLightSensorController);
+
         mClamperController.onDisplayChanged(mMockDisplayDeviceData);
 
+        verify(mMockLightSensorController, never()).stop();
         verify(mMockLightSensorController).restart();
     }
 
     @Test
+    public void testOnDisplayChanged_doesNotRestartLightSensor_screenOff() {
+        when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+        mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+                false, STATE_OFF);
+        reset(mMockLightSensorController);
+
+        mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+
+        verify(mMockLightSensorController, never()).restart();
+        verify(mMockLightSensorController).stop();
+    }
+
+    @Test
     public void testClamp_AppliesModifier() {
         float initialBrightness = 0.2f;
         boolean initialSlowChange = true;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 709f83b..73dcfe7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -36,6 +36,7 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.ConditionVariable;
+import android.os.Handler;
 import android.os.Parcel;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
@@ -81,8 +82,9 @@
                     .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
 
     private MockClock mMockClock = mStatsRule.getMockClock();
-    private MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock);
+    private MonotonicClock mMonotonicClock = mStatsRule.getMonotonicClock();
     private Context mContext;
+    private PowerStatsStore mPowerStatsStore;
 
     @Before
     public void setup() throws IOException {
@@ -93,6 +95,9 @@
         } else {
             mContext = InstrumentationRegistry.getContext();
         }
+        mPowerStatsStore = spy(new PowerStatsStore(
+                new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()),
+                mStatsRule.getHandler()));
     }
 
     @Test
@@ -274,10 +279,7 @@
         powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
                 true);
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
-                powerAttributor, mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
-                mMonotonicClock);
+        BatteryUsageStatsProvider provider = createBatteryUsageStatsProvider(0);
 
         return provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT);
     }
@@ -331,30 +333,30 @@
         BatteryStats.HistoryItem item;
 
         assertThat(item = iterator.next()).isNotNull();
-        assertHistoryItem(item,
+        assertHistoryItem(batteryStats, item,
                 BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 1_000_000);
 
         assertThat(item = iterator.next()).isNotNull();
-        assertHistoryItem(item,
+        assertHistoryItem(batteryStats, item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 1_000_000);
         assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isNotEqualTo(0);
 
         assertThat(item = iterator.next()).isNotNull();
-        assertHistoryItem(item,
+        assertHistoryItem(batteryStats, item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 2_000_000);
         assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isEqualTo(0);
 
         assertThat(item = iterator.next()).isNotNull();
-        assertHistoryItem(item,
+        assertHistoryItem(batteryStats, item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
                 "foo", APP_UID, 3_600_000, 90, 3_000_000);
 
         assertThat(item = iterator.next()).isNotNull();
-        assertHistoryItem(item,
+        assertHistoryItem(batteryStats, item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
                 "foo", APP_UID, 3_600_000, 90, 3_001_000);
@@ -441,14 +443,15 @@
             assertThat(item.eventTag.string).startsWith(uid + " ");
             assertThat(item.batteryChargeUah).isEqualTo(3_600_000);
             assertThat(item.batteryLevel).isEqualTo(90);
-            assertThat(item.time).isEqualTo((long) 1_000_000);
+            assertThat(item.time).isEqualTo(batteryStats.getMonotonicStartTime() + 1_000_000);
         }
 
         assertThat(expectedUid).isEqualTo(200);
     }
 
-    private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
-            String tag, int uid, int batteryChargeUah, int batteryLevel, long elapsedTimeMs) {
+    private void assertHistoryItem(MockBatteryStatsImpl batteryStats, BatteryStats.HistoryItem item,
+            int command, int eventCode, String tag, int uid, int batteryChargeUah, int batteryLevel,
+            long elapsedTimeMs) {
         assertThat(item.cmd).isEqualTo(command);
         assertThat(item.eventCode).isEqualTo(eventCode);
         if (tag == null) {
@@ -460,7 +463,7 @@
         assertThat(item.batteryChargeUah).isEqualTo(batteryChargeUah);
         assertThat(item.batteryLevel).isEqualTo(batteryLevel);
 
-        assertThat(item.time).isEqualTo(elapsedTimeMs);
+        assertThat(item.time).isEqualTo(batteryStats.getMonotonicStartTime() + elapsedTimeMs);
     }
 
     @Test
@@ -566,38 +569,66 @@
 
         assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
         assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * MINUTE_IN_MS);
-        assertThat(stats.getAggregateBatteryConsumer(
-                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
-                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
-                .isWithin(0.0001)
-                .of(180.0);  // 360 mA * 0.5 hours
-        assertThat(stats.getAggregateBatteryConsumer(
-                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
-                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
-                .isEqualTo((10 + 20) * MINUTE_IN_MS);
-        final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream()
-                .filter(uid -> uid.getUid() == APP_UID).findFirst().get();
-        assertThat(uidBatteryConsumer
-                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
-                .isWithin(0.1)
-                .of(180.0);
+        assertBatteryConsumer(stats, 180.0, (10 + 20) * MINUTE_IN_MS);
+        assertBatteryConsumer(stats, APP_UID, 180.0, (10 + 20) * MINUTE_IN_MS);
 
         stats.close();
     }
 
     @Test
     public void accumulateBatteryUsageStats() throws Throwable {
-        accumulateBatteryUsageStats(10000000, 1);
+        MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+        accumulateBatteryUsageStats(batteryStats, 10000000, 0);
         // Accumulate every 200 bytes of battery history
-        accumulateBatteryUsageStats(200, 2);
-        accumulateBatteryUsageStats(50, 5);
+        accumulateBatteryUsageStats(batteryStats, 200, 2);
+        accumulateBatteryUsageStats(batteryStats, 50, 4);
         // Accumulate on every invocation of accumulateBatteryUsageStats
-        accumulateBatteryUsageStats(0, 7);
+        accumulateBatteryUsageStats(batteryStats, 0, 7);
     }
 
-    private void accumulateBatteryUsageStats(int accumulatedBatteryUsageStatsSpanSize,
-            int expectedNumberOfUpdates) throws Throwable {
-        BatteryStatsImpl batteryStats = spy(mStatsRule.getBatteryStats());
+    @Test
+    public void getAccumulatedBatteryUsageStats() throws Throwable {
+        MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+        // Only accumulate the first 25 minutes
+        accumulateBatteryUsageStats(batteryStats, 200, 1);
+
+        BatteryUsageStatsProvider batteryUsageStatsProvider = createBatteryUsageStatsProvider(200);
+
+        // At this point the last stored accumulated stats are `115 - 30 = 85` minutes old
+        BatteryUsageStats stats = batteryUsageStatsProvider.getBatteryUsageStats(batteryStats,
+                new BatteryUsageStatsQuery.Builder()
+                        .accumulated()
+                        .setMaxStatsAgeMs(90 * MINUTE_IN_MS)
+                        .build());
+
+        assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
+        assertThat(stats.getStatsEndTimestamp()).isEqualTo(30 * MINUTE_IN_MS);
+        assertBatteryConsumer(stats, 60.0, 10 * MINUTE_IN_MS);
+        assertBatteryConsumer(stats, APP_UID, 60.0, 10 * MINUTE_IN_MS);
+
+        stats.close();
+
+        // Now force the usage stats to catch up to the current time
+        stats = batteryUsageStatsProvider.getBatteryUsageStats(batteryStats,
+                new BatteryUsageStatsQuery.Builder()
+                        .accumulated()
+                        .setMaxStatsAgeMs(5 * MINUTE_IN_MS)
+                        .build());
+
+        assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
+        assertThat(stats.getStatsEndTimestamp()).isEqualTo(115 * MINUTE_IN_MS);
+        assertBatteryConsumer(stats, 360.0, 60 * MINUTE_IN_MS);
+        assertBatteryConsumer(stats, APP_UID, 360.0, 60 * MINUTE_IN_MS);
+
+        stats.close();
+    }
+
+    private void accumulateBatteryUsageStats(MockBatteryStatsImpl batteryStatsImpl,
+            int accumulatedBatteryUsageStatsSpanSize, int expectedNumberOfUpdates)
+            throws Throwable {
+        Handler handler = mStatsRule.getHandler();
+        MockBatteryStatsImpl batteryStats = spy(batteryStatsImpl);
         // Note - these two are in microseconds
         when(batteryStats.computeBatteryTimeRemaining(anyLong())).thenReturn(111_000L);
         when(batteryStats.computeChargeTimeRemaining(anyLong())).thenReturn(777_000L);
@@ -610,82 +641,76 @@
             batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
         }
 
-        PowerStatsStore powerStatsStore = spy(new PowerStatsStore(
-                new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()),
-                mStatsRule.getHandler()));
-        powerStatsStore.reset();
+        mPowerStatsStore.reset();
 
         int[] count = new int[1];
         doAnswer(inv -> {
             count[0]++;
-            return null;
-        }).when(powerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class));
+            return inv.callRealMethod();
+        }).when(mPowerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class));
 
-        MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext,
-                powerStatsStore, mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(),
-                () -> 3500);
-        for (int powerComponentId = 0; powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT;
-                powerComponentId++) {
-            powerAttributor.setPowerComponentSupported(powerComponentId, true);
-        }
-        powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_ANY, true);
+        BatteryUsageStatsProvider batteryUsageStatsProvider = createBatteryUsageStatsProvider(
+                accumulatedBatteryUsageStatsSpanSize);
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
-                powerAttributor, mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), powerStatsStore,
-                accumulatedBatteryUsageStatsSpanSize, mMockClock, mMonotonicClock);
+        batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
 
-        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
-
+        setTime(10 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOnLocked(APP_UID,
                     10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
         }
 
-        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+        batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
 
+        setTime(20 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
         }
 
-        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+        batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
 
+        setTime(30 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOnLocked(APP_UID,
                     30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
         }
 
-        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+        batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
 
+        // Make sure the accumulated stats are computed and saved before generating more history
+        mStatsRule.waitForBackgroundThread();
+
+        setTime(50 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
         }
         setTime(55 * MINUTE_IN_MS);
 
-        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+        batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
 
         // This section has not been saved yet, but should be added to the accumulated totals
+        setTime(80 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOnLocked(APP_UID,
                     80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
         }
 
-        provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+        batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
 
+        setTime(110 * MINUTE_IN_MS);
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOffLocked(APP_UID,
                     110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
         }
         setTime(115 * MINUTE_IN_MS);
 
-        // Pick up the remainder of battery history that has not yet been accumulated
-        provider.accumulateBatteryUsageStats(batteryStats);
+        batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
 
         mStatsRule.waitForBackgroundThread();
 
-        BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats,
+        BatteryUsageStats stats = batteryUsageStatsProvider.getBatteryUsageStats(batteryStats,
                 new BatteryUsageStatsQuery.Builder().accumulated().build());
 
         assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
@@ -696,29 +721,55 @@
         assertThat(stats.getBatteryCapacity()).isEqualTo(4000);  // from PowerProfile
 
         // Total: 10 + 20 + 30 = 60
-        assertThat(stats.getAggregateBatteryConsumer(
-                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+        assertBatteryConsumer(stats, 360.0, 60 * MINUTE_IN_MS);
+        assertBatteryConsumer(stats, APP_UID, 360.0, 60 * MINUTE_IN_MS);
+        stats.close();
+
+        mStatsRule.waitForBackgroundThread();
+
+        assertThat(count[0]).isEqualTo(expectedNumberOfUpdates);
+    }
+
+    private BatteryUsageStatsProvider createBatteryUsageStatsProvider(
+            int accumulatedBatteryUsageStatsSpanSize) {
+        MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext,
+                mPowerStatsStore, mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(),
+                () -> 3500);
+        for (int powerComponentId = 0; powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+                powerComponentId++) {
+            powerAttributor.setPowerComponentSupported(powerComponentId, true);
+        }
+        powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_ANY, true);
+
+        return new BatteryUsageStatsProvider(mContext, powerAttributor,
+                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), mPowerStatsStore,
+                accumulatedBatteryUsageStatsSpanSize, mMockClock, mMonotonicClock);
+    }
+
+    private static void assertBatteryConsumer(BatteryUsageStats stats, double expectedPowerMah,
+            long expectedDurationMs) {
+        AggregateBatteryConsumer aggregatedConsumer = stats.getAggregateBatteryConsumer(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+        assertThat(aggregatedConsumer
                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
                 .isWithin(0.0001)
-                .of(360.0);  // 360 mA * 1.0 hour
-        assertThat(stats.getAggregateBatteryConsumer(
-                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .of(expectedPowerMah);
+        assertThat(aggregatedConsumer
                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
-                .isEqualTo(60 * MINUTE_IN_MS);
+                .isEqualTo(expectedDurationMs);
+    }
 
+    private static void assertBatteryConsumer(BatteryUsageStats stats, int uid,
+            double expectedPowerMah, long expectedDurationMs) {
         final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream()
-                .filter(uid -> uid.getUid() == APP_UID).findFirst().get();
+                .filter(u -> u.getUid() == uid).findFirst().get();
         assertThat(uidBatteryConsumer
                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
                 .isWithin(0.1)
-                .of(360.0);
+                .of(expectedPowerMah);
         assertThat(uidBatteryConsumer
                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
-                .isEqualTo(60 * MINUTE_IN_MS);
-
-        assertThat(count[0]).isEqualTo(expectedNumberOfUpdates);
-
-        stats.close();
+                .isEqualTo(expectedDurationMs);
     }
 
     private void setTime(long timeMs) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index a3c7ece..9e7e0b6 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -41,6 +41,7 @@
 import android.util.Xml;
 
 import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.power.EnergyConsumerStats;
 
@@ -65,6 +66,7 @@
 
     private final PowerProfile mPowerProfile;
     private final MockClock mMockClock = new MockClock();
+    private final MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock);
     private String mTestName;
     private boolean mCreateTempDirectory;
     private File mHistoryDir;
@@ -118,7 +120,7 @@
             clearDirectory();
         }
         mBatteryStats = new MockBatteryStatsImpl(mBatteryStatsConfigBuilder.build(),
-                mMockClock, mHistoryDir, mHandler, new PowerStatsUidResolver());
+                mMockClock, mMonotonicClock, mHistoryDir, mHandler, new PowerStatsUidResolver());
         mBatteryStats.setPowerProfile(mPowerProfile);
         mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
         synchronized (mBatteryStats) {
@@ -144,6 +146,10 @@
         return mMockClock;
     }
 
+    public MonotonicClock getMonotonicClock() {
+        return mMonotonicClock;
+    }
+
     public Handler getHandler() {
         return mHandler;
     }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index b374a32..9a38209 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -77,9 +77,15 @@
                 new PowerStatsUidResolver());
     }
 
-    MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory,
-            Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
-        super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
+    MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock,
+            File historyDirectory, Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
+        this(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
+                powerStatsUidResolver);
+    }
+
+    MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, MonotonicClock monotonicClock,
+            File historyDirectory, Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
+        super(config, clock, monotonicClock, historyDirectory, handler,
                 mock(PlatformIdleStateCallback.class), mock(EnergyStatsRetriever.class),
                 mock(UserInfoProvider.class), mockPowerProfile(),
                 new CpuScalingPolicies(new SparseArray<>(), new SparseArray<>()),
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
index 38fe613..d243f92 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
@@ -408,7 +408,7 @@
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
                 new String[]{"cu570m"},
                 /* includeProcessStateData */ true, true, true, /* powerThreshold */ 0);
-        exportAggregatedPowerStats(builder, 3700, 6700);
+        exportAggregatedPowerStats(builder, 3700, 7500);
 
         BatteryUsageStats actual = builder.build();
         String message = "Actual BatteryUsageStats: " + actual;
diff --git a/services/tests/security/intrusiondetection/AndroidManifest.xml b/services/tests/security/intrusiondetection/AndroidManifest.xml
index b30710d..d58e0d8 100644
--- a/services/tests/security/intrusiondetection/AndroidManifest.xml
+++ b/services/tests/security/intrusiondetection/AndroidManifest.xml
@@ -19,18 +19,10 @@
 
        <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"     />
        <uses-permission android:name="android.permission.INTERNET"/>
+       <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
 
     <application android:testOnly="true" android:debuggable="true" android:usesCleartextTraffic="true">
       <uses-library android:name="android.test.runner"/>
-        <receiver android:name="com.android.server.security.intrusiondetection.IntrusionDetectionAdminReceiver"
-             android:permission="android.permission.BIND_DEVICE_ADMIN"
-             android:exported="true">
-            <meta-data android:name="android.app.device_admin"
-                 android:resource="@xml/device_admin"/>
-            <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
-            </intent-filter>
-        </receiver>
     </application>
 
     <queries>
diff --git a/services/tests/security/intrusiondetection/AndroidTest.xml b/services/tests/security/intrusiondetection/AndroidTest.xml
index 6489dea4a..0d21158 100644
--- a/services/tests/security/intrusiondetection/AndroidTest.xml
+++ b/services/tests/security/intrusiondetection/AndroidTest.xml
@@ -24,6 +24,10 @@
         <option name="install-arg" value="-t" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="setprop intrusiondetection_service_name com.android.coretests.apps.testapp/.TestLoggingService" />
+    </target_preparer>
+
     <option name="test-tag" value="IntrusionDetectionServiceTests" />
     <test class="com.android.tradefed.testtype.InstrumentationTest" >
         <option name="package" value="com.android.server.security.intrusiondetection.tests" />
diff --git a/services/tests/security/intrusiondetection/res/xml/device_admin.xml b/services/tests/security/intrusiondetection/res/xml/device_admin.xml
deleted file mode 100644
index f8cd8f0..0000000
--- a/services/tests/security/intrusiondetection/res/xml/device_admin.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2024 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
-</device-admin>
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
index e505ebe..5cba6b2 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
@@ -16,12 +16,12 @@
 
 package com.android.server.security.intrusiondetection;
 
+import static android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE;
 import static android.Manifest.permission.INTERNET;
 import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE;
 import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
@@ -45,7 +45,6 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.IBinder;
-import android.os.Bundle;
 import android.os.Looper;
 import android.os.PermissionEnforcer;
 import android.os.RemoteException;
@@ -61,17 +60,13 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
-import com.android.bedstead.harrier.annotations.AfterClass;
-import com.android.bedstead.harrier.annotations.BeforeClass;
 import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser;
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.devicepolicy.DeviceOwner;
-import com.android.bedstead.nene.exceptions.NeneException;
 import com.android.bedstead.permissions.CommonPermissions;
 import com.android.bedstead.permissions.PermissionContext;
 import com.android.bedstead.permissions.annotations.EnsureHasPermission;
 import com.android.coretests.apps.testapp.LocalIntrusionDetectionEventTransport;
-import com.android.internal.infra.AndroidFuture;
 import com.android.server.ServiceThread;
 
 import org.junit.Before;
@@ -129,28 +124,6 @@
         "com.android.coretests.apps.testapp";
     private static final String TEST_SERVICE = TEST_PKG + ".TestLoggingService";
 
-    @BeforeClass
-    public static void setDeviceOwner() {
-        ComponentName admin =
-                new ComponentName(
-                        ApplicationProvider.getApplicationContext(),
-                        IntrusionDetectionAdminReceiver.class);
-        try {
-            sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin);
-        } catch (NeneException e) {
-            fail("Failed to set device owner " + admin.flattenToString() + ": " + e);
-        }
-    }
-
-    @AfterClass
-    public static void removeDeviceOwner() {
-        try {
-            sDeviceOwner.remove();
-        } catch (NeneException e) {
-            fail("Failed to remove device owner : " + e);
-        }
-    }
-
     @SuppressLint("VisibleForTests")
     @Before
     public void setUp() throws Exception {
@@ -159,6 +132,7 @@
         mPermissionEnforcer = new FakePermissionEnforcer();
         mPermissionEnforcer.grant(READ_INTRUSION_DETECTION_STATE);
         mPermissionEnforcer.grant(MANAGE_INTRUSION_DETECTION_STATE);
+        mPermissionEnforcer.grant(BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE);
 
         mTestLooper = new TestLooper();
         mLooper = mTestLooper.getLooper();
@@ -178,6 +152,7 @@
     }
 
     @Test
+    @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void testRemoveStateCallback_NoPermission() {
         mPermissionEnforcer.revoke(READ_INTRUSION_DETECTION_STATE);
         StateCallback scb = new StateCallback();
@@ -229,6 +204,7 @@
     }
 
     @Test
+    @Ignore("Unit test does not run as system service UID")
     public void testRemoveStateCallback() throws RemoteException {
         mIntrusionDetectionService.setState(STATE_DISABLED);
         StateCallback scb1 = new StateCallback();
@@ -239,7 +215,6 @@
         assertEquals(STATE_DISABLED, scb1.mState);
         assertEquals(STATE_DISABLED, scb2.mState);
 
-        doReturn(true).when(mDataAggregator).initialize();
         doReturn(true).when(mIntrusionDetectionEventTransportConnection).initialize();
 
         mIntrusionDetectionService.getBinderService().removeStateCallback(scb2);
@@ -252,6 +227,7 @@
         assertNull(ccb.mErrorCode);
     }
 
+    @Ignore("Unit test does not run as system service UID")
     @Test
     public void testEnable_FromDisabled_TwoStateCallbacks() throws RemoteException {
         mIntrusionDetectionService.setState(STATE_DISABLED);
@@ -412,39 +388,13 @@
     }
 
     @Test
-    @RequireRunOnSystemUser
-    public void testDataSources_Initialize_HasDeviceOwner() throws Exception {
-        NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator);
-        SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator);
-
-        assertTrue(networkLogSource.initialize());
-        assertTrue(securityLogSource.initialize());
-    }
-
-    @Test
-    @RequireRunOnSystemUser
-    public void testDataSources_Initialize_NoDeviceOwner() throws Exception {
-        NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator);
-        SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator);
-        ComponentName admin = sDeviceOwner.componentName();
-
-        try {
-            sDeviceOwner.remove();
-            assertFalse(networkLogSource.initialize());
-            assertFalse(securityLogSource.initialize());
-        } finally {
-            sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin);
-        }
-    }
-
-    @Test
+    @Ignore("Unit test does not run as system service UID")
     @RequireRunOnSystemUser
     @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void testDataAggregator_AddSecurityEvent() throws Exception {
         mIntrusionDetectionService.setState(STATE_ENABLED);
         ServiceThread mockThread = spy(ServiceThread.class);
         mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
-        assertTrue(mDataAggregator.initialize());
 
         // SecurityLogging generates a number of events and callbacks, so create a latch to wait for
         // the given event.
@@ -488,12 +438,12 @@
 
     @Test
     @RequireRunOnSystemUser
+    @Ignore("Unit test does not run as system service UID")
     @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void testDataAggregator_AddNetworkEvent() throws Exception {
         mIntrusionDetectionService.setState(STATE_ENABLED);
         ServiceThread mockThread = spy(ServiceThread.class);
         mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
-        assertTrue(mDataAggregator.initialize());
 
         // Network logging may log multiple and callbacks, so create a latch to wait for
         // the given event.
@@ -578,6 +528,9 @@
     }
 
     @Test
+    @RequireRunOnSystemUser
+    @EnsureHasPermission(
+            android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
     public void test_StartIntrusionDetectionEventTransportService() {
         final String TAG = "test_StartIntrusionDetectionEventTransportService";
         ServiceConnection serviceConnection = null;
@@ -639,6 +592,20 @@
         return serviceConnection;
     }
 
+    @Test
+    @RequireRunOnSystemUser
+    @EnsureHasPermission(
+            android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
+    public void testIntrusionDetectionEventTransportConnection_isValidAndBinds()
+            throws InterruptedException {
+        IntrusionDetectionEventTransportConnection intrusionDetectionEventTransportConnection =
+                new IntrusionDetectionEventTransportConnection(mContext);
+        // In a real scenario, the connection will be initialized by the service.
+        // Just to show that the connection is valid and able to bind,
+        // we initialize it here.
+        assertTrue(intrusionDetectionEventTransportConnection.initialize());
+    }
+
     private class MockInjector implements IntrusionDetectionService.Injector {
         private final Context mContext;
 
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
index 7cc75ab..a1a7e29 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
@@ -16,9 +16,11 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.coretests.apps.testapp">
+     <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
 
     <application>
         <service android:name=".TestLoggingService"
-                  android:exported="true" />
+                  android:exported="true"
+                  android:permission="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
     </application>
 </manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index a8544f6..5862ac6 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -26,6 +26,7 @@
 import android.content.ContextWrapper
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.pm.PackageManager
 import android.content.pm.UserInfo
 import android.os.Handler
 import android.os.PersistableBundle
@@ -59,6 +60,7 @@
     @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
 
     @Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal
+    @Mock private lateinit var mockPackageManager: PackageManager
     @Mock private lateinit var mockUserManagerInternal: UserManagerInternal
 
     private lateinit var context: Context
@@ -68,7 +70,7 @@
     @Before
     fun setUp() {
         context = InstrumentationRegistry.getInstrumentation().context
-        context = SupervisionContextWrapper(context)
+        context = SupervisionContextWrapper(context, mockPackageManager)
 
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
         LocalServices.addService(DevicePolicyManagerInternal::class.java, mockDpmInternal)
@@ -137,6 +139,31 @@
     }
 
     @Test
+    fun isActiveSupervisionApp_supervisionUid_supervisionEnabled_returnsTrue() {
+        whenever(mockPackageManager.getPackagesForUid(APP_UID))
+            .thenReturn(arrayOf(systemSupervisionPackage))
+        service.setSupervisionEnabledForUser(USER_ID, true)
+
+        assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isTrue()
+    }
+
+    @Test
+    fun isActiveSupervisionApp_supervisionUid_supervisionNotEnabled_returnsFalse() {
+        whenever(mockPackageManager.getPackagesForUid(APP_UID))
+            .thenReturn(arrayOf(systemSupervisionPackage))
+        service.setSupervisionEnabledForUser(USER_ID, false)
+
+        assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse()
+    }
+
+    @Test
+    fun isActiveSupervisionApp_notSupervisionUid_returnsFalse() {
+        whenever(mockPackageManager.getPackagesForUid(APP_UID)).thenReturn(arrayOf())
+
+        assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse()
+    }
+
+    @Test
     fun setSupervisionEnabledForUser() {
         assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
 
@@ -191,6 +218,7 @@
 
     private companion object {
         const val USER_ID = 100
+        val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
     }
 }
 
@@ -198,9 +226,12 @@
  * A context wrapper that allows broadcast intents to immediately invoke the receivers without
  * performing checks on the sending user.
  */
-private class SupervisionContextWrapper(val context: Context) : ContextWrapper(context) {
+private class SupervisionContextWrapper(val context: Context, val pkgManager: PackageManager) :
+    ContextWrapper(context) {
     val interceptors = mutableListOf<Pair<BroadcastReceiver, IntentFilter>>()
 
+    override fun getPackageManager() = pkgManager
+
     override fun registerReceiverForAllUsers(
         receiver: BroadcastReceiver?,
         filter: IntentFilter,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 90bf1d3..41b5fde 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -17314,6 +17314,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
                 .build();
 
@@ -17327,6 +17328,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
                 .build();
         StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0,
@@ -17339,6 +17341,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
                 .build();
         StatusBarNotification sbn2 = new StatusBarNotification(PKG_O, PKG_O, 7, null, UID_O, 0,
@@ -17388,6 +17391,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
                 .build();
 
@@ -17420,6 +17424,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
                 .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
                 .build();
@@ -17434,6 +17439,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
                 .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
                 .build();
@@ -17483,6 +17489,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
                 .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
                 .build();
@@ -17515,6 +17522,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .build();
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
                 n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -17543,6 +17551,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .build();
 
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
@@ -17570,6 +17579,7 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
+                .setOngoing(true)
                 .build();
 
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index aa99250..25b9f4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -717,13 +717,13 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+    @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
     public void testVisibleTasks_excludedFromRecents() {
         testVisibleTasks_excludedFromRecents_internal();
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+    @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
     public void testVisibleTasks_excludedFromRecents_withRefactorFlag() {
         testVisibleTasks_excludedFromRecents_internal();
     }
@@ -767,13 +767,13 @@
 
     @Test
     @Ignore("b/342627272")
-    @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+    @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
     public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask() {
         testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal();
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+    @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
     public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_withRefactorFlag() {
         testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal();
     }
@@ -816,13 +816,13 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+    @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
     public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() {
         testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal();
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+    @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
     public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible_withRefactorFlag() {
         testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal();
     }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 010a322..e7c9e92 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -48,6 +48,7 @@
 import android.app.PendingIntent;
 import android.app.UidObserver;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.supervision.SupervisionManagerInternal;
 import android.app.usage.AppLaunchEstimateInfo;
 import android.app.usage.AppStandbyInfo;
 import android.app.usage.BroadcastResponseStatsList;
@@ -230,6 +231,7 @@
     // Do not use directly. Call getDpmInternal() instead
     DevicePolicyManagerInternal mDpmInternal;
     // Do not use directly. Call getShortcutServiceInternal() instead
+    SupervisionManagerInternal mSupervisionManagerInternal;
     ShortcutServiceInternal mShortcutServiceInternal;
 
     private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
@@ -439,6 +441,9 @@
             // initialize mDpmInternal
             getDpmInternal();
             // initialize mShortcutServiceInternal
+            if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
+                getSupervisionManagerInternal();
+            }
             getShortcutServiceInternal();
             mResponseStatsTracker.onSystemServicesReady(getContext());
 
@@ -604,6 +609,15 @@
         return mDpmInternal;
     }
 
+    @Nullable
+    private SupervisionManagerInternal getSupervisionManagerInternal() {
+        if (mSupervisionManagerInternal == null) {
+            mSupervisionManagerInternal =
+                    LocalServices.getService(SupervisionManagerInternal.class);
+        }
+        return mSupervisionManagerInternal;
+    }
+
     private ShortcutServiceInternal getShortcutServiceInternal() {
         if (mShortcutServiceInternal == null) {
             mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class);
@@ -753,6 +767,16 @@
                 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED);
     }
 
+    private boolean isSupervisionEnabled(int callingUid) {
+        if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
+            SupervisionManagerInternal smInternal = getSupervisionManagerInternal();
+            return smInternal != null && smInternal.isActiveSupervisionApp(callingUid);
+        } else {
+            DevicePolicyManagerInternal dpmInternal = getDpmInternal();
+            return dpmInternal != null && dpmInternal.isActiveSupervisionApp(callingUid);
+        }
+    }
+
     private static void deleteRecursively(final File path) {
         if (path.isDirectory()) {
             final File[] files = path.listFiles();
@@ -2929,10 +2953,9 @@
                 long timeLimitMs, long timeUsedMs, PendingIntent callbackIntent,
                 String callingPackage) {
             final int callingUid = Binder.getCallingUid();
-            final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
             if (!hasPermissions(
                     Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)
-                    && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) {
+                    && !isSupervisionEnabled(callingUid)) {
                 throw new SecurityException("Caller must be the active supervision app or "
                         + "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions");
             }
@@ -2956,10 +2979,9 @@
         @Override
         public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) {
             final int callingUid = Binder.getCallingUid();
-            final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
             if (!hasPermissions(
                     Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)
-                    && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) {
+                    && !isSupervisionEnabled(callingUid)) {
                 throw new SecurityException("Caller must be the active supervision app or "
                         + "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions");
             }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index d89c9c1..7082f00 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2049,11 +2049,15 @@
     /**
      * Ends the foreground call on the device.
      * <p>
-     * If there is a ringing call, calling this method rejects the ringing call.  Otherwise the
+     * If there is a ringing call, calling this method rejects the ringing call. Otherwise, the
      * foreground call is ended.
      * <p>
      * Note: this method CANNOT be used to end ongoing emergency calls and will return {@code false}
      * if an attempt is made to end an emergency call.
+     * <p>
+     * Note: If the foreground call on this device is self-managed, this method will only end
+     * the call if the caller of this method is privileged (i.e. system, shell, or root) or system
+     * UI.
      *
      * @return {@code true} if there is a call which will be rejected or terminated, {@code false}
      * otherwise.
@@ -2082,6 +2086,9 @@
      * the incoming call requests.  This means, for example, that an incoming call requesting
      * {@link VideoProfile#STATE_BIDIRECTIONAL} will be answered, accepting that state.
      *
+     * If the ringing incoming call is self-managed, this method will only accept the call if the
+     * caller of this method is privileged (i.e. system, shell, or root) or system UI.
+     *
      * @deprecated Companion apps for wearable devices should use the {@link InCallService} API
      * instead.
      */
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl
index eda0f5b..c4a3670 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl
@@ -24,5 +24,5 @@
  * Allows the TelecomLoaderService to pass additional dependencies required for creation.
  */
 interface ITelecomLoader {
-    ITelecomService createTelecomService(IInternalServiceRetriever retriever);
+    ITelecomService createTelecomService(IInternalServiceRetriever retriever, String sysUiName);
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 59cb5ff..e5f1841 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9756,9 +9756,8 @@
      * }</pre>
      * <p>
      * This config is empty by default.
-     * @hide
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE =
             "regional_satellite_earfcn_bundle";
 
@@ -9885,15 +9884,14 @@
             "remove_satellite_plmn_in_manual_network_scan_bool";
 
     /**
-     * This value is used to set the max datagram size, if the value is not available then the
-     * default one will be used.
-     * If key is {@code true}, retrieve the max datagram value and use this value always,
-     * {@code false} the default value from the modem will be used.
+     * This value is used to set the max datagram size in bytes.
+     * If the value is not available then the default value will be used.
      *
-     * @hide
+     * The default value is 255 bytes.
      */
-    public static final String KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE =
-            "satellite_sos_max_datagram_size";
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+    public static final String KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT =
+            "satellite_sos_max_datagram_size_bytes_int";
 
     /** @hide */
     @IntDef({
@@ -10062,9 +10060,9 @@
 
     /**
      * The display name that will be used for satellite functionality within the UI.
-     * The default string value for this is "Satellite".
-     * @hide
+     * The default string value is empty string.
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_SATELLITE_DISPLAY_NAME_STRING = "satellite_display_name_string";
 
     /**
@@ -10183,10 +10181,8 @@
      * A string array containing the list of messaging apps that support satellite.
      *
      * The default value contains only "com.google.android.apps.messaging"
-     *
-     * @hide
      */
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY =
             "satellite_supported_msg_apps_string_array";
 
@@ -11458,7 +11454,7 @@
         sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3});
         sDefaults.putInt(KEY_WEAR_CONNECTIVITY_BT_TO_CELL_DELAY_MS_INT, -1);
         sDefaults.putInt(KEY_WEAR_CONNECTIVITY_EXTEND_BT_TO_CELL_DELAY_ON_WIFI_MS_INT, -1);
-        sDefaults.putInt(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE, 255);
+        sDefaults.putInt(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT, 255);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index e01b10e..bb4ce6e 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -16,12 +16,15 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.Annotation.OverrideNetworkType;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -94,6 +97,12 @@
 
     private final boolean mIsRoaming;
 
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    private final boolean mIsNtn;
+
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    private final boolean mIsSatelliteConstrainedData;
+
     /**
      * Constructor
      *
@@ -106,7 +115,7 @@
     @Deprecated
     public TelephonyDisplayInfo(@NetworkType int networkType,
             @OverrideNetworkType int overrideNetworkType) {
-        this(networkType, overrideNetworkType, false);
+        this(networkType, overrideNetworkType, false, false, false);
     }
 
     /**
@@ -118,12 +127,37 @@
      *
      * @hide
      */
+    @Deprecated
     public TelephonyDisplayInfo(@NetworkType int networkType,
             @OverrideNetworkType int overrideNetworkType,
             boolean isRoaming) {
         mNetworkType = networkType;
         mOverrideNetworkType = overrideNetworkType;
         mIsRoaming = isRoaming;
+        mIsNtn = false;
+        mIsSatelliteConstrainedData = false;
+    }
+
+    /**
+     * Constructor
+     *
+     * @param networkType Current packet-switching cellular network type
+     * @param overrideNetworkType The override network type
+     * @param isRoaming True if the device is roaming after override.
+     * @param isNtn True if the device is camped to non-terrestrial network.
+     * @param isSatelliteConstrainedData True if the device satellite internet is bandwidth
+     *        constrained.
+     *
+     * @hide
+     */
+    public TelephonyDisplayInfo(@NetworkType int networkType,
+            @OverrideNetworkType int overrideNetworkType,
+            boolean isRoaming, boolean isNtn, boolean isSatelliteConstrainedData) {
+        mNetworkType = networkType;
+        mOverrideNetworkType = overrideNetworkType;
+        mIsRoaming = isRoaming;
+        mIsNtn = isNtn;
+        mIsSatelliteConstrainedData = isSatelliteConstrainedData;
     }
 
     /** @hide */
@@ -131,6 +165,8 @@
         mNetworkType = p.readInt();
         mOverrideNetworkType = p.readInt();
         mIsRoaming = p.readBoolean();
+        mIsNtn = p.readBoolean();
+        mIsSatelliteConstrainedData = p.readBoolean();
     }
 
     /**
@@ -170,11 +206,34 @@
         return mIsRoaming;
     }
 
+    /**
+     * Get whether the satellite internet is with bandwidth constrained capability set.
+     *
+     * @return {@code true} if satellite internet is connected with bandwidth constrained
+     *         capability else {@code false}.
+     * @hide
+     */
+    public boolean isSatelliteConstrainedData() {
+        return mIsSatelliteConstrainedData;
+    }
+
+    /**
+     * Get whether the network is a non-terrestrial network.
+     *
+     * @return {@code true} if network is a non-terrestrial network else {@code false}.
+     * @hide
+     */
+    public boolean isNtn() {
+        return mIsNtn;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mNetworkType);
         dest.writeInt(mOverrideNetworkType);
         dest.writeBoolean(mIsRoaming);
+        dest.writeBoolean(mIsNtn);
+        dest.writeBoolean(mIsSatelliteConstrainedData);
     }
 
     public static final @NonNull Parcelable.Creator<TelephonyDisplayInfo> CREATOR =
@@ -202,12 +261,15 @@
         TelephonyDisplayInfo that = (TelephonyDisplayInfo) o;
         return mNetworkType == that.mNetworkType
                 && mOverrideNetworkType == that.mOverrideNetworkType
-                && mIsRoaming == that.mIsRoaming;
+                && mIsRoaming == that.mIsRoaming
+                && mIsNtn == that.mIsNtn
+                && mIsSatelliteConstrainedData == that.mIsSatelliteConstrainedData;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming);
+        return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming, mIsNtn,
+                mIsSatelliteConstrainedData);
     }
 
     /**
@@ -233,6 +295,8 @@
     public String toString() {
         return "TelephonyDisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
                 + ", overrideNetwork=" + overrideNetworkTypeToString(mOverrideNetworkType)
-                + ", isRoaming=" + mIsRoaming + "}";
+                + ", isRoaming=" + mIsRoaming
+                + ", isNtn=" + mIsNtn
+                + ", isSatelliteConstrainedData=" + mIsSatelliteConstrainedData + "}";
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
index 5e276aa..0a1eedf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
@@ -18,6 +18,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 
 import com.android.internal.telephony.flags.Flags;
 
@@ -26,13 +27,14 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
 public interface SatelliteDisallowedReasonsCallback {
 
     /**
      * Called when disallowed reason of satellite has changed.
      * @param disallowedReasons Integer array of disallowed reasons.
      */
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    void onSatelliteDisallowedReasonsChanged(@NonNull int[] disallowedReasons);
+    void onSatelliteDisallowedReasonsChanged(
+            @NonNull @SatelliteManager.SatelliteDisallowedReason int[] disallowedReasons);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 1025c15..0f23f33 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -560,6 +560,8 @@
      * There is no valid satellite subscription selected.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_RESULT_NO_VALID_SATELLITE_SUBSCRIPTION = 30;
 
     /** @hide */
@@ -2393,8 +2395,9 @@
      *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public void requestSatelliteAccessConfigurationForCurrentLocation(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<SatelliteAccessConfiguration, SatelliteException> callback) {
@@ -2507,7 +2510,7 @@
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
      *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
-     *                 will return the time after which the satellite will be visible.
+     *                 will return the selected NB IOT satellite subscription ID.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
@@ -2515,6 +2518,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void requestSelectedNbIotSatelliteSubscriptionId(
             @NonNull @CallbackExecutor Executor executor,
@@ -2574,6 +2579,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @SatelliteResult public int registerForSelectedNbIotSatelliteSubscriptionChanged(
             @NonNull @CallbackExecutor Executor executor,
@@ -2619,6 +2626,8 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void unregisterForSelectedNbIotSatelliteSubscriptionChanged(
             @NonNull SelectedNbIotSatelliteSubscriptionCallback callback) {
@@ -2897,27 +2906,22 @@
     /**
      * Returns list of disallowed reasons of satellite.
      *
-     * @return list of disallowed reasons of satellite.
+     * @return Integer array of disallowed reasons.
      *
      * @throws SecurityException     if caller doesn't have required permission.
      * @throws IllegalStateException if Telephony process isn't available.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @SatelliteDisallowedReason
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     @NonNull
-    public List<Integer> getSatelliteDisallowedReasons() {
+    public int[] getSatelliteDisallowedReasons() {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                int[] receivedArray = telephony.getSatelliteDisallowedReasons();
-                if (receivedArray.length == 0) {
-                    logd("receivedArray is empty, create empty list");
-                    return new ArrayList<>();
-                } else {
-                    return Arrays.stream(receivedArray).boxed().collect(Collectors.toList());
-                }
+                return telephony.getSatelliteDisallowedReasons();
             } else {
                 throw new IllegalStateException("Telephony service is null.");
             }
@@ -2925,7 +2929,7 @@
             loge("getSatelliteDisallowedReasons() RemoteException: " + ex);
             ex.rethrowAsRuntimeException();
         }
-        return new ArrayList<>();
+        return new int[0];
     }
 
     /**
@@ -2938,8 +2942,9 @@
      * @throws IllegalStateException if Telephony process is not available.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public void registerForSatelliteDisallowedReasonsChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteDisallowedReasonsCallback callback) {
@@ -2981,8 +2986,9 @@
      * @throws IllegalStateException if Telephony process is not available.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public void unregisterForSatelliteDisallowedReasonsChanged(
             @NonNull SatelliteDisallowedReasonsCallback callback) {
         Objects.requireNonNull(callback);
@@ -3606,8 +3612,8 @@
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
      *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
-     *                 will return display name of the satellite feature in string format. Defaults
-     *                 to satellite. If the request is not successful,
+     *                 will return display name of the satellite feature in string format. Default
+     *                 display name is "Satellite". If the request is not successful,
      *                 {@link OutcomeReceiver#onError(Throwable)} will return an error with
      *                 a SatelliteException.
      *
@@ -3615,10 +3621,12 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void requestSatelliteDisplayName(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<String, SatelliteException> callback) {
+            @NonNull OutcomeReceiver<CharSequence, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -3630,7 +3638,7 @@
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
                         if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_SATELLITE_DISPLAY_NAME)) {
-                                String satelliteDisplayName =
+                                CharSequence satelliteDisplayName =
                                         resultData.getString(KEY_SATELLITE_DISPLAY_NAME);
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
                                         callback.onResult(satelliteDisplayName)));
diff --git a/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java b/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java
index d896554..76caef3 100644
--- a/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java
+++ b/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java
@@ -16,11 +16,18 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * A callback class for selected satellite subscription changed events.
  *
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
 public interface SelectedNbIotSatelliteSubscriptionCallback {
     /**
      * Called when the selected satellite subscription has changed.
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index af87bf7..49616c3 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -83,6 +83,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
@@ -112,6 +113,7 @@
 
     private final TestClock mTestClock = new TestClock();
     private TestLooper mTestLooper;
+    private Executor mTestExecutor;
     private Context mSpyContext;
     // Keep track of all created watchdogs to apply device config changes
     private List<PackageWatchdog> mAllocatedWatchdogs;
@@ -141,6 +143,7 @@
                 Manifest.permission.WRITE_DEVICE_CONFIG,
                 Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
         mTestLooper = new TestLooper();
+        mTestExecutor = mTestLooper.getNewExecutor();
         mSpyContext = spy(InstrumentationRegistry.getContext());
         when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
@@ -231,31 +234,37 @@
             watchdog.noteBoot();
         }
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
     }
@@ -272,6 +281,7 @@
             watchdog.noteBoot();
         }
 
+        mTestLooper.dispatchAll();
         verify(rollbackObserver).onExecuteBootLoopMitigation(1);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
 
@@ -281,6 +291,7 @@
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rollbackObserver).onExecuteBootLoopMitigation(2);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
 
@@ -289,6 +300,7 @@
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
     }
 
@@ -305,18 +317,21 @@
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
         verify(rollbackObserver).onExecuteBootLoopMitigation(1);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -326,24 +341,28 @@
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
         verify(rollbackObserver).onExecuteBootLoopMitigation(2);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -352,6 +371,7 @@
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -362,6 +382,7 @@
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
     }
@@ -379,12 +400,14 @@
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
         verify(rollbackObserver).onExecuteBootLoopMitigation(1);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -394,6 +417,7 @@
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
         verify(rollbackObserver).onExecuteBootLoopMitigation(2);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -402,6 +426,7 @@
 
         watchdog.noteBoot();
 
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
         verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -412,6 +437,7 @@
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
+        mTestLooper.dispatchAll();
         verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
         verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
     }
@@ -739,14 +765,14 @@
         } catch (PackageManager.NameNotFoundException e) {
             throw new RuntimeException(e);
         }
-        watchdog.registerHealthObserver(rollbackObserver);
+        watchdog.registerHealthObserver(rollbackObserver, mTestExecutor);
         return rollbackObserver;
     }
     RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) {
         setCrashRecoveryPropRescueBootCount(0);
         RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext));
         assertFalse(RescueParty.isRebootPropertySet());
-        watchdog.registerHealthObserver(rescuePartyObserver);
+        watchdog.registerHealthObserver(rescuePartyObserver, mTestExecutor);
         return rescuePartyObserver;
     }
 
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 5a8a6be..c64dc72 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -53,6 +53,7 @@
 import android.provider.DeviceConfig;
 import android.util.AtomicFile;
 import android.util.LongArrayQueue;
+import android.util.Slog;
 import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
@@ -88,6 +89,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
@@ -119,6 +121,7 @@
 
     private final TestClock mTestClock = new TestClock();
     private TestLooper mTestLooper;
+    private Executor mTestExecutor;
     private Context mSpyContext;
     // Keep track of all created watchdogs to apply device config changes
     private List<PackageWatchdog> mAllocatedWatchdogs;
@@ -155,6 +158,7 @@
                 Manifest.permission.WRITE_DEVICE_CONFIG,
                 Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
         mTestLooper = new TestLooper();
+        mTestExecutor = mTestLooper.getNewExecutor();
         mSpyContext = spy(InstrumentationRegistry.getContext());
         when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
@@ -226,7 +230,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -242,8 +247,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer2, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
                         new VersionedPackage(APP_B, VERSION_CODE)),
@@ -260,7 +267,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
         watchdog.unregisterHealthObserver(observer);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -276,8 +284,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer2, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
         watchdog.unregisterHealthObserver(observer2);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -294,7 +304,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
         moveTimeForwardAndDispatch(SHORT_DURATION);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -310,8 +321,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer2, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), LONG_DURATION);
         moveTimeForwardAndDispatch(SHORT_DURATION);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -330,13 +343,14 @@
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
         // Start observing APP_A
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then advance time half-way
         moveTimeForwardAndDispatch(SHORT_DURATION / 2);
 
         // Start observing APP_A again
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then advance time such that it should have expired were it not for the second observation
         moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
@@ -358,15 +372,17 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+        watchdog1.registerHealthObserver(observer1, mTestExecutor);
+        watchdog1.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog1.registerHealthObserver(observer2, mTestExecutor);
+        watchdog1.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
         // Then advance time and run IO Handler so file is saved
         mTestLooper.dispatchAll();
         // Then start a new watchdog
         PackageWatchdog watchdog2 = createWatchdog();
         // Then resume observer1 and observer2
-        watchdog2.registerHealthObserver(observer1);
-        watchdog2.registerHealthObserver(observer2);
+        watchdog2.registerHealthObserver(observer1, mTestExecutor);
+        watchdog2.registerHealthObserver(observer2, mTestExecutor);
         raiseFatalFailureAndDispatch(watchdog2,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
                         new VersionedPackage(APP_B, VERSION_CODE)),
@@ -374,6 +390,7 @@
 
         // We should receive failed packages as expected to ensure observers are persisted and
         // resumed correctly
+        mTestLooper.dispatchAll();
         assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
         assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
     }
@@ -387,8 +404,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer2, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A below the threshold
         for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) {
@@ -414,9 +433,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer2, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_B), SHORT_DURATION);
 
         // Then fail APP_C (not observed) above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -448,7 +468,8 @@
                 }
             };
 
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A (different version) above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -477,13 +498,17 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         // Start observing for all impact observers
-        watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+        watchdog.registerHealthObserver(observerNone, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
                 SHORT_DURATION);
-        watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+        watchdog.registerHealthObserver(observerHigh, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
                 SHORT_DURATION);
-        watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+        watchdog.registerHealthObserver(observerMid, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
                 SHORT_DURATION);
-        watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+        watchdog.registerHealthObserver(observerLow, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
                 SHORT_DURATION);
 
         // Then fail all apps above the threshold
@@ -523,13 +548,17 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         // Start observing for all impact observers
-        watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+        watchdog.registerHealthObserver(observerNone, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
                 SHORT_DURATION);
-        watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+        watchdog.registerHealthObserver(observerHigh, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
                 SHORT_DURATION);
-        watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+        watchdog.registerHealthObserver(observerMid, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
                 SHORT_DURATION);
-        watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+        watchdog.registerHealthObserver(observerLow, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
                 SHORT_DURATION);
 
         // Then fail all apps above the threshold
@@ -577,8 +606,10 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing for observerFirst and observerSecond with failure handling
-        watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
-        watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(observerFirst, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(observerSecond, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
 
         // Then fail APP_A above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -641,8 +672,10 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing for observerFirst and observerSecond with failure handling
-        watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
-        watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(observerFirst, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(observerSecond, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
 
         // Then fail APP_A above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -709,8 +742,10 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
 
         // Start observing for observer1 and observer2 with failure handling
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer2, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -731,8 +766,10 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
 
         // Start observing for observer1 and observer2 with failure handling
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer2, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -762,8 +799,10 @@
         // Start observing with explicit health checks for APP_A and APP_B respectively
         // with observer1 and observer2
         controller.setSupportedPackages(Arrays.asList(APP_A, APP_B));
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer2, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
 
         // Run handler so requests are dispatched to the controller
         mTestLooper.dispatchAll();
@@ -779,7 +818,8 @@
         // Observer3 didn't exist when we got the explicit health check above, so
         // it starts out with a non-passing explicit health check and has to wait for a pass
         // otherwise it would be notified of APP_A failure on expiry
-        watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer3, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer3, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then expire observers
         moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -809,8 +849,9 @@
 
         // Start observing with explicit health checks for APP_A and APP_B
         controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_B), LONG_DURATION);
 
         // Run handler so requests are dispatched to the controller
         mTestLooper.dispatchAll();
@@ -846,7 +887,7 @@
         // Then set new supported packages
         controller.setSupportedPackages(Arrays.asList(APP_C));
         // Start observing APP_A and APP_C; only APP_C has support for explicit health checks
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
 
         // Run handler so requests/cancellations are dispatched to the controller
         mTestLooper.dispatchAll();
@@ -877,7 +918,8 @@
         // package observation duration == LONG_DURATION
         // health check duration == SHORT_DURATION (set by default in the TestController)
         controller.setSupportedPackages(Arrays.asList(APP_A));
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), LONG_DURATION);
 
         // Then APP_A has exceeded health check duration
         moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -908,7 +950,8 @@
         // package observation duration == SHORT_DURATION / 2
         // health check duration == SHORT_DURATION (set by default in the TestController)
         controller.setSupportedPackages(Arrays.asList(APP_A));
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
 
         // Forward time to expire the observation duration
         moveTimeForwardAndDispatch(SHORT_DURATION / 2);
@@ -981,7 +1024,7 @@
         // Start observing with failure handling
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
-        wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+        wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
 
         // Notify of NetworkStack failure
         mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1001,7 +1044,7 @@
         // Start observing with failure handling
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
-        wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+        wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
 
         // Notify of NetworkStack failure
         mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1022,7 +1065,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
         // Fail APP_A below the threshold which should not trigger package failures
         for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
             watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1050,7 +1094,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
         watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
@@ -1075,15 +1120,16 @@
     }
 
     /**
-     * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
-     * an invalid durationMs.
+     * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is
+     * offered an invalid durationMs.
      */
     @Test
     public void testInvalidMonitoringDuration_beforeExpiry() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
         // Note: Don't move too close to the expiration time otherwise the handler will be thrashed
         // by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
         // small timeouts.
@@ -1097,15 +1143,16 @@
     }
 
     /**
-     * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
-     * an invalid durationMs.
+     * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is
+     * offered an invalid durationMs.
      */
     @Test
     public void testInvalidMonitoringDuration_afterExpiry() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1127,7 +1174,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
         // Raise 2 failures at t=0 and t=900 respectively
         watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1154,8 +1202,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer2, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
@@ -1174,7 +1224,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
@@ -1194,7 +1245,8 @@
         persistentObserver.setPersistent(true);
         persistentObserver.setMayObservePackages(true);
 
-        watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
+        watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1212,7 +1264,8 @@
         persistentObserver.setPersistent(true);
         persistentObserver.setMayObservePackages(false);
 
-        watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
+        watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1223,13 +1276,15 @@
     /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
     @Test
     public void testBootLoopDetection_meetsThreshold() {
+        Slog.w("hrm1243", "I should definitely be here try 1 ");
         mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(bootObserver);
+        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
+        mTestLooper.dispatchAll();
         assertThat(bootObserver.mitigatedBootLoop()).isTrue();
     }
 
@@ -1237,10 +1292,11 @@
     public void testBootLoopDetection_meetsThresholdRecoverability() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(bootObserver);
+        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
         for (int i = 0; i < 15; i++) {
             watchdog.noteBoot();
         }
+        mTestLooper.dispatchAll();
         assertThat(bootObserver.mitigatedBootLoop()).isTrue();
     }
 
@@ -1252,10 +1308,11 @@
     public void testBootLoopDetection_doesNotMeetThreshold() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(bootObserver);
+        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
             watchdog.noteBoot();
         }
+        mTestLooper.dispatchAll();
         assertThat(bootObserver.mitigatedBootLoop()).isFalse();
     }
 
@@ -1268,10 +1325,11 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
-        watchdog.registerHealthObserver(bootObserver);
+        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
             watchdog.noteBoot();
         }
+        mTestLooper.dispatchAll();
         assertThat(bootObserver.mitigatedBootLoop()).isFalse();
     }
 
@@ -1286,11 +1344,12 @@
         bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
         TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
         bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
-        watchdog.registerHealthObserver(bootObserver1);
-        watchdog.registerHealthObserver(bootObserver2);
+        watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
+        watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
+        mTestLooper.dispatchAll();
         assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
         assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
     }
@@ -1302,11 +1361,12 @@
         bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
         TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
         bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
-        watchdog.registerHealthObserver(bootObserver1);
-        watchdog.registerHealthObserver(bootObserver2);
+        watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
+        watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
         for (int i = 0; i < 15; i++) {
             watchdog.noteBoot();
         }
+        mTestLooper.dispatchAll();
         assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
         assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
     }
@@ -1319,7 +1379,7 @@
         mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(bootObserver);
+        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
         for (int i = 0; i < 4; i++) {
             for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) {
                 watchdog.noteBoot();
@@ -1333,7 +1393,7 @@
                 watchdog.noteBoot();
             }
         }
-
+        mTestLooper.dispatchAll();
         assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
     }
 
@@ -1342,7 +1402,7 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
-        watchdog.registerHealthObserver(bootObserver);
+        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
         for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) {
             watchdog.noteBoot();
         }
@@ -1358,7 +1418,7 @@
         for (int i = 0; i < 4; i++) {
                 watchdog.noteBoot();
         }
-
+        mTestLooper.dispatchAll();
         assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
     }
 
@@ -1370,7 +1430,8 @@
     public void testNullFailedPackagesList() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
-        watchdog.startObservingHealth(observer1, List.of(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(observer1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer1, List.of(APP_A), LONG_DURATION);
 
         raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH);
         assertThat(observer1.mMitigatedPackages).isEmpty();
@@ -1388,18 +1449,18 @@
         PackageWatchdog watchdog = createWatchdog(testController, true);
 
         TestObserver testObserver1 = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(testObserver1);
-        watchdog.startObservingHealth(testObserver1, List.of(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(testObserver1, mTestExecutor);
+        watchdog.startExplicitHealthCheck(testObserver1, List.of(APP_A), LONG_DURATION);
         mTestLooper.dispatchAll();
 
         TestObserver testObserver2 = new TestObserver(OBSERVER_NAME_2);
-        watchdog.registerHealthObserver(testObserver2);
-        watchdog.startObservingHealth(testObserver2, List.of(APP_B), LONG_DURATION);
+        watchdog.registerHealthObserver(testObserver2, mTestExecutor);
+        watchdog.startExplicitHealthCheck(testObserver2, List.of(APP_B), LONG_DURATION);
         mTestLooper.dispatchAll();
 
         TestObserver testObserver3 = new TestObserver(OBSERVER_NAME_3);
-        watchdog.registerHealthObserver(testObserver3);
-        watchdog.startObservingHealth(testObserver3, List.of(APP_C), LONG_DURATION);
+        watchdog.registerHealthObserver(testObserver3, mTestExecutor);
+        watchdog.startExplicitHealthCheck(testObserver3, List.of(APP_C), LONG_DURATION);
         mTestLooper.dispatchAll();
 
         watchdog.unregisterHealthObserver(testObserver1);
@@ -1431,14 +1492,15 @@
     public void testFailureHistoryIsPreserved() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
-        watchdog.startObservingHealth(observer, List.of(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, List.of(APP_A), SHORT_DURATION);
         for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
             watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
                     PackageWatchdog.FAILURE_REASON_UNKNOWN);
         }
         mTestLooper.dispatchAll();
         assertThat(observer.mMitigatedPackages).isEmpty();
-        watchdog.startObservingHealth(observer, List.of(APP_A), LONG_DURATION);
+        watchdog.startExplicitHealthCheck(observer, List.of(APP_A), LONG_DURATION);
         watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
         mTestLooper.dispatchAll();
@@ -1453,7 +1515,8 @@
     public void testMitigationSlidingWindow() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
-        watchdog.startObservingHealth(observer, List.of(APP_A),
+        watchdog.registerHealthObserver(observer, mTestExecutor);
+        watchdog.startExplicitHealthCheck(observer, List.of(APP_A),
                 PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS * 2);
 
 
@@ -1895,6 +1958,7 @@
         }
 
         public boolean onExecuteBootLoopMitigation(int level) {
+            Slog.w("hrm1243", "I'm here " + level);
             mMitigatedBootLoop = true;
             mBootMitigationCounts.add(level);
             return true;