Merge "Update time window message." into main
diff --git a/Ravenwood.bp b/Ravenwood.bp
index d13c4d7..93febca4 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -33,6 +33,7 @@
         "@$(location ravenwood/ravenwood-standard-options.txt) " +
 
         "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
+        "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
 
         "--out-impl-jar $(location ravenwood.jar) " +
 
@@ -56,6 +57,7 @@
         "hoststubgen_dump.txt",
 
         "hoststubgen_framework-minus-apex.log",
+        "hoststubgen_framework-minus-apex_stats.csv",
     ],
     visibility: ["//visibility:private"],
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index 10b6081..41f96ee 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1052,6 +1052,7 @@
     field public static final int label = 16842753; // 0x1010001
     field public static final int labelFor = 16843718; // 0x10103c6
     field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235
+    field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") public static final int languageSettingsActivity;
     field public static final int languageTag = 16844040; // 0x1010508
     field public static final int largeHeap = 16843610; // 0x101035a
     field public static final int largeScreens = 16843398; // 0x1010286
@@ -5425,6 +5426,7 @@
 
   @FlaggedApi("android.security.content_uri_permission_apis") public final class ComponentCaller {
     ctor public ComponentCaller(@NonNull android.os.IBinder, @Nullable android.os.IBinder);
+    method public int checkContentUriPermission(@NonNull android.net.Uri, int);
     method @Nullable public String getPackage();
     method public int getUid();
   }
@@ -6990,6 +6992,7 @@
     field public static final String ACTION_APP_BLOCK_STATE_CHANGED = "android.app.action.APP_BLOCK_STATE_CHANGED";
     field public static final String ACTION_AUTOMATIC_ZEN_RULE = "android.app.action.AUTOMATIC_ZEN_RULE";
     field public static final String ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED = "android.app.action.AUTOMATIC_ZEN_RULE_STATUS_CHANGED";
+    field @FlaggedApi("android.app.modes_api") public static final String ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED = "android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED";
     field public static final String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
     field public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
     field public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
@@ -7010,6 +7013,7 @@
     field public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
     field public static final String EXTRA_NOTIFICATION_CHANNEL_GROUP_ID = "android.app.extra.NOTIFICATION_CHANNEL_GROUP_ID";
     field public static final String EXTRA_NOTIFICATION_CHANNEL_ID = "android.app.extra.NOTIFICATION_CHANNEL_ID";
+    field @FlaggedApi("android.app.modes_api") public static final String EXTRA_NOTIFICATION_POLICY = "android.app.extra.NOTIFICATION_POLICY";
     field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
     field public static final int IMPORTANCE_HIGH = 4; // 0x4
     field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -11254,6 +11258,7 @@
     field public static final String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST";
     field public static final String CATEGORY_VOICE = "android.intent.category.VOICE";
     field public static final String CATEGORY_VR_HOME = "android.intent.category.VR_HOME";
+    field @FlaggedApi("android.service.chooser.chooser_album_text") public static final int CHOOSER_CONTENT_TYPE_ALBUM = 1; // 0x1
     field @NonNull public static final android.os.Parcelable.Creator<android.content.Intent> CREATOR;
     field public static final String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
     field public static final String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE";
@@ -11275,6 +11280,7 @@
     field public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
     field public static final String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
     field public static final String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
+    field @FlaggedApi("android.service.chooser.chooser_album_text") public static final String EXTRA_CHOOSER_CONTENT_TYPE_HINT = "android.intent.extra.CHOOSER_CONTENT_TYPE_HINT";
     field public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS = "android.intent.extra.CHOOSER_CUSTOM_ACTIONS";
     field public static final String EXTRA_CHOOSER_MODIFY_SHARE_ACTION = "android.intent.extra.CHOOSER_MODIFY_SHARE_ACTION";
     field public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
@@ -22700,10 +22706,12 @@
     field @Deprecated public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 2141391872; // 0x7fa30c00
     field @Deprecated public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100
     field public static final String FEATURE_AdaptivePlayback = "adaptive-playback";
+    field @FlaggedApi("android.media.codec.dynamic_color_aspects") public static final String FEATURE_DynamicColorAspects = "dynamic-color-aspects";
     field public static final String FEATURE_DynamicTimestamp = "dynamic-timestamp";
     field public static final String FEATURE_EncodingStatistics = "encoding-statistics";
     field public static final String FEATURE_FrameParsing = "frame-parsing";
     field public static final String FEATURE_HdrEditing = "hdr-editing";
+    field @FlaggedApi("android.media.codec.hlg_editing") public static final String FEATURE_HlgEditing = "hlg-editing";
     field public static final String FEATURE_IntraRefresh = "intra-refresh";
     field public static final String FEATURE_LowLatency = "low-latency";
     field public static final String FEATURE_MultipleFrames = "multiple-frames";
@@ -24398,6 +24406,7 @@
   }
 
   public final class MediaRouter2 {
+    method @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") public void cancelScanRequest(@NonNull android.media.MediaRouter2.ScanToken);
     method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
     method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
     method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
@@ -24409,6 +24418,7 @@
     method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
     method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceUpdatedCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.media.RouteListingPreference>);
     method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback);
+    method @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") @NonNull public android.media.MediaRouter2.ScanToken requestScan(@NonNull android.media.MediaRouter2.ScanRequest);
     method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
     method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference);
     method public boolean showSystemOutputSwitcher();
@@ -24445,6 +24455,7 @@
     method @NonNull public android.media.RoutingSessionInfo getRoutingSessionInfo();
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectableRoutes();
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectedRoutes();
+    method @FlaggedApi("com.android.media.flags.enable_get_transferable_routes") @NonNull public java.util.List<android.media.MediaRoute2Info> getTransferableRoutes();
     method public int getVolume();
     method public int getVolumeHandling();
     method public int getVolumeMax();
@@ -24455,6 +24466,19 @@
     method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferInitiatedBySelf();
   }
 
+  @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") public static final class MediaRouter2.ScanRequest {
+    method public boolean isScreenOffScan();
+  }
+
+  public static final class MediaRouter2.ScanRequest.Builder {
+    ctor public MediaRouter2.ScanRequest.Builder();
+    method @NonNull public android.media.MediaRouter2.ScanRequest build();
+    method @NonNull public android.media.MediaRouter2.ScanRequest.Builder setScreenOffScan(boolean);
+  }
+
+  @FlaggedApi("com.android.media.flags.enable_screen_off_scanning") public static final class MediaRouter2.ScanToken {
+  }
+
   public abstract static class MediaRouter2.TransferCallback {
     ctor public MediaRouter2.TransferCallback();
     method public void onStop(@NonNull android.media.MediaRouter2.RoutingController);
@@ -36953,6 +36977,7 @@
     field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA";
     field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
+    field @FlaggedApi("android.provider.backup_tasks_settings_screen") public static final String ACTION_REQUEST_RUN_BACKUP_JOBS = "android.settings.REQUEST_RUN_BACKUP_JOBS";
     field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
     field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
@@ -55851,6 +55876,7 @@
   public final class InputMethodInfo implements android.os.Parcelable {
     ctor public InputMethodInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     ctor public InputMethodInfo(String, String, CharSequence, String);
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") @Nullable public android.content.Intent createImeLanguageSettingsActivityIntent();
     method @Nullable public android.content.Intent createStylusHandwritingSettingsActivityIntent();
     method public int describeContents();
     method public void dump(android.util.Printer, String);
@@ -55870,6 +55896,7 @@
     method public boolean supportsStylusHandwriting();
     method public boolean suppressesSpellChecker();
     method public void writeToParcel(android.os.Parcel, int);
+    field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") public static final String ACTION_IME_LANGUAGE_SETTINGS = "android.view.inputmethod.action.IME_LANGUAGE_SETTINGS";
     field public static final String ACTION_STYLUS_HANDWRITING_SETTINGS = "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS";
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InputMethodInfo> CREATOR;
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4aae6e0..aca003d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4359,10 +4359,12 @@
   public final class DomainVerificationManager {
     method @Nullable @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.SortedSet<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String);
+    method @FlaggedApi("android.content.pm.relative_reference_intent_filters") @NonNull public java.util.Map<java.lang.String,java.util.List<android.content.UriRelativeFilterGroup>> getUriRelativeFilterGroups(@NonNull String, @NonNull java.util.List<java.lang.String>);
     method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames();
     method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
     method @CheckResult @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @CheckResult @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.relative_reference_intent_filters") @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setUriRelativeFilterGroups(@NonNull String, @NonNull java.util.Map<java.lang.String,java.util.List<android.content.UriRelativeFilterGroup>>);
     field public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; // 0x1
     field public static final int ERROR_UNABLE_TO_APPROVE = 3; // 0x3
     field public static final int ERROR_UNKNOWN_DOMAIN = 2; // 0x2
@@ -5676,6 +5678,7 @@
     method @NonNull public android.hardware.location.ContextHubInfo getAttachedHub();
     method @IntRange(from=0, to=65535) public int getId();
     method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessageToNanoApp(@NonNull android.hardware.location.NanoAppMessage);
+    method @FlaggedApi("android.chre.flags.reliable_message") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> sendReliableMessageToNanoApp(@NonNull android.hardware.location.NanoAppMessage);
   }
 
   public class ContextHubClientCallback {
@@ -5711,6 +5714,7 @@
     method public String getToolchain();
     method public int getToolchainVersion();
     method public String getVendor();
+    method @FlaggedApi("android.chre.flags.reliable_message") public boolean supportsReliableMessages();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.ContextHubInfo> CREATOR;
   }
@@ -5794,6 +5798,7 @@
     field public static final int RESULT_FAILED_BAD_PARAMS = 2; // 0x2
     field public static final int RESULT_FAILED_BUSY = 4; // 0x4
     field public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8; // 0x8
+    field @FlaggedApi("android.chre.flags.reliable_message") public static final int RESULT_FAILED_NOT_SUPPORTED = 9; // 0x9
     field public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7; // 0x7
     field public static final int RESULT_FAILED_TIMEOUT = 6; // 0x6
     field public static final int RESULT_FAILED_UNINITIALIZED = 3; // 0x3
@@ -5803,6 +5808,7 @@
     field public static final int TYPE_ENABLE_NANOAPP = 2; // 0x2
     field public static final int TYPE_LOAD_NANOAPP = 0; // 0x0
     field public static final int TYPE_QUERY_NANOAPPS = 4; // 0x4
+    field @FlaggedApi("android.chre.flags.reliable_message") public static final int TYPE_RELIABLE_MESSAGE = 5; // 0x5
     field public static final int TYPE_UNLOAD_NANOAPP = 1; // 0x1
   }
 
@@ -5985,12 +5991,17 @@
 
   public final class NanoAppMessage implements android.os.Parcelable {
     method public static android.hardware.location.NanoAppMessage createMessageFromNanoApp(long, int, byte[], boolean);
+    method @FlaggedApi("android.chre.flags.reliable_message") @NonNull public static android.hardware.location.NanoAppMessage createMessageFromNanoApp(long, int, @NonNull byte[], boolean, boolean, int);
     method public static android.hardware.location.NanoAppMessage createMessageToNanoApp(long, int, byte[]);
     method public int describeContents();
     method public byte[] getMessageBody();
+    method @FlaggedApi("android.chre.flags.reliable_message") public int getMessageSequenceNumber();
     method public int getMessageType();
     method public long getNanoAppId();
     method public boolean isBroadcastMessage();
+    method @FlaggedApi("android.chre.flags.reliable_message") public boolean isReliable();
+    method @FlaggedApi("android.chre.flags.reliable_message") public void setIsReliable(boolean);
+    method @FlaggedApi("android.chre.flags.reliable_message") public void setMessageSequenceNumber(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR;
   }
@@ -11306,6 +11317,7 @@
     method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource);
     method @RequiresPermission(value=android.Manifest.permission.UPDATE_APP_OPS_STATS, conditional=true) public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
     method public void finishDataDelivery(@NonNull String, @NonNull android.content.AttributionSource);
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages();
     method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages();
     method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public int getPermissionFlags(@NonNull String, @NonNull String, @NonNull String);
@@ -11325,6 +11337,14 @@
     field public static final int PERMISSION_SOFT_DENIED = 1; // 0x1
   }
 
+  @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public static final class PermissionManager.PermissionState implements android.os.Parcelable {
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public int describeContents();
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public int getFlags();
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public boolean isGranted();
+    method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull public static final android.os.Parcelable.Creator<android.permission.PermissionManager.PermissionState> CREATOR;
+  }
+
   public static final class PermissionManager.SplitPermissionInfo {
     method @NonNull public java.util.List<java.lang.String> getNewPermissions();
     method @NonNull public String getSplitPermission();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index fc095d4..288374d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3894,7 +3894,7 @@
   }
 
   public final class InputMethodInfo implements android.os.Parcelable {
-    ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String);
+    ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, @NonNull String);
     ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
     field public static final int COMPONENT_NAME_MAX_LENGTH = 1000; // 0x3e8
     field public static final int MAX_IMES_PER_PACKAGE = 20; // 0x14
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index b938f0f..c1181f5 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -2189,6 +2189,8 @@
     New API must be flagged with @FlaggedApi: field android.view.accessibility.AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
 UnflaggedApi: android.view.animation.AnimationUtils#lockAnimationClock(long, long):
     New API must be flagged with @FlaggedApi: method android.view.animation.AnimationUtils.lockAnimationClock(long,long)
+UnflaggedApi: android.view.inputmethod.InputMethodInfo#InputMethodInfo(String, String, CharSequence, String, String, boolean, String):
+    New API must be flagged with @FlaggedApi: constructor android.view.inputmethod.InputMethodInfo(String,String,CharSequence,String,String,boolean,String)
 UnflaggedApi: android.view.inputmethod.InputMethodManager#getEnabledInputMethodListAsUser(android.os.UserHandle):
     New API must be flagged with @FlaggedApi: method android.view.inputmethod.InputMethodManager.getEnabledInputMethodListAsUser(android.os.UserHandle)
 UnflaggedApi: android.view.inputmethod.InputMethodManager#getEnabledInputMethodSubtypeListAsUser(String, boolean, android.os.UserHandle):
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 2a7dbab..e7e3a85 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -364,12 +364,12 @@
     public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14;
 
     /**
-     * The user has performed an down and left gesture on the touch screen.
+     * The user has performed a down and left gesture on the touch screen.
      */
     public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15;
 
     /**
-     * The user has performed an down and right gesture on the touch screen.
+     * The user has performed a down and right gesture on the touch screen.
      */
     public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
 
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index fc342fa..8bb2857 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -647,11 +647,44 @@
 
     private int mObservedMotionEventSources = 0;
 
+    // Default values for each dynamic property
+    // LINT.IfChange(dynamic_property_defaults)
+    private final DynamicPropertyDefaults mDynamicPropertyDefaults;
+
+    private static class DynamicPropertyDefaults {
+        private final int mEventTypesDefault;
+        private final List<String> mPackageNamesDefault;
+        private final int mFeedbackTypeDefault;
+        private final long mNotificationTimeoutDefault;
+        private final int mFlagsDefault;
+        private final int mNonInteractiveUiTimeoutDefault;
+        private final int mInteractiveUiTimeoutDefault;
+        private final int mMotionEventSourcesDefault;
+        private final int mObservedMotionEventSourcesDefault;
+
+        DynamicPropertyDefaults(AccessibilityServiceInfo info) {
+            mEventTypesDefault = info.eventTypes;
+            if (info.packageNames != null) {
+                mPackageNamesDefault = List.of(info.packageNames);
+            } else {
+                mPackageNamesDefault = null;
+            }
+            mFeedbackTypeDefault = info.feedbackType;
+            mNotificationTimeoutDefault = info.notificationTimeout;
+            mNonInteractiveUiTimeoutDefault = info.mNonInteractiveUiTimeout;
+            mInteractiveUiTimeoutDefault = info.mInteractiveUiTimeout;
+            mFlagsDefault = info.flags;
+            mMotionEventSourcesDefault = info.mMotionEventSources;
+            mObservedMotionEventSourcesDefault = info.mObservedMotionEventSources;
+        }
+    }
+    // LINT.ThenChange(:dynamic_property_reset)
+
     /**
      * Creates a new instance.
      */
     public AccessibilityServiceInfo() {
-        /* do nothing */
+        mDynamicPropertyDefaults = new DynamicPropertyDefaults(this);
     }
 
     /**
@@ -758,7 +791,7 @@
                 }
             }
             peekedValue = asAttributes.peekValue(
-                com.android.internal.R.styleable.AccessibilityService_summary);
+                    com.android.internal.R.styleable.AccessibilityService_summary);
             if (peekedValue != null) {
                 mSummaryResId = peekedValue.resourceId;
                 CharSequence nonLocalizedSummary = peekedValue.coerceToString();
@@ -793,10 +826,38 @@
             if (parser != null) {
                 parser.close();
             }
+
+            mDynamicPropertyDefaults = new DynamicPropertyDefaults(this);
         }
     }
 
     /**
+     * Resets all dynamically configurable properties to their default values.
+     *
+     * @hide
+     */
+    // LINT.IfChange(dynamic_property_reset)
+    public void resetDynamicallyConfigurableProperties() {
+        eventTypes = mDynamicPropertyDefaults.mEventTypesDefault;
+        if (mDynamicPropertyDefaults.mPackageNamesDefault == null) {
+            packageNames = null;
+        } else {
+            packageNames = mDynamicPropertyDefaults.mPackageNamesDefault.toArray(new String[0]);
+        }
+        feedbackType = mDynamicPropertyDefaults.mFeedbackTypeDefault;
+        notificationTimeout = mDynamicPropertyDefaults.mNotificationTimeoutDefault;
+        mNonInteractiveUiTimeout = mDynamicPropertyDefaults.mNonInteractiveUiTimeoutDefault;
+        mInteractiveUiTimeout = mDynamicPropertyDefaults.mInteractiveUiTimeoutDefault;
+        flags = mDynamicPropertyDefaults.mFlagsDefault;
+        mMotionEventSources = mDynamicPropertyDefaults.mMotionEventSourcesDefault;
+        if (Flags.motionEventObserving()) {
+            mObservedMotionEventSources = mDynamicPropertyDefaults
+                    .mObservedMotionEventSourcesDefault;
+        }
+    }
+    // LINT.ThenChange(:dynamic_property_update)
+
+    /**
      * Updates the properties that an AccessibilityService can change dynamically.
      * <p>
      * Note: A11y services targeting APIs > Q, it cannot update flagRequestAccessibilityButton
@@ -808,6 +869,7 @@
      *
      * @hide
      */
+    // LINT.IfChange(dynamic_property_update)
     public void updateDynamicallyConfigurableProperties(IPlatformCompat platformCompat,
             AccessibilityServiceInfo other) {
         if (isRequestAccessibilityButtonChangeEnabled(platformCompat)) {
@@ -828,6 +890,7 @@
         // NOTE: Ensure that only properties that are safe to be modified by the service itself
         // are included here (regardless of hidden setters, etc.).
     }
+    // LINT.ThenChange(:dynamic_property_defaults)
 
     private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) {
         if (mResolveInfo == null) {
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index b8bd030..a59f04b 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -17,13 +17,16 @@
 package android.app;
 
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.os.UserHandle.getCallingUserId;
 
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.content.ComponentName;
+import android.content.ContentProvider;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
@@ -296,6 +299,18 @@
         }
     }
 
+    /** Checks if the app that launched the activity has access to the URI. */
+    public int checkActivityCallerContentUriPermission(IBinder activityToken, IBinder callerToken,
+            Uri uri, int modeFlags) {
+        try {
+            return getActivityClientController().checkActivityCallerContentUriPermission(
+                    activityToken, callerToken, ContentProvider.getUriWithoutUserId(uri), modeFlags,
+                    ContentProvider.getUserIdFromUri(uri, getCallingUserId()));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     public void setRequestedOrientation(IBinder token, int requestedOrientation) {
         try {
             getActivityClientController().setRequestedOrientation(token, requestedOrientation);
diff --git a/core/java/android/app/COMPONENT_CALLER_OWNERS b/core/java/android/app/COMPONENT_CALLER_OWNERS
new file mode 100644
index 0000000..f8fdeae
--- /dev/null
+++ b/core/java/android/app/COMPONENT_CALLER_OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 315013
[email protected]
[email protected]
+
+include /services/core/java/com/android/server/uri/OWNERS
diff --git a/core/java/android/app/ComponentCaller.java b/core/java/android/app/ComponentCaller.java
index 583408e..a440dbc 100644
--- a/core/java/android/app/ComponentCaller.java
+++ b/core/java/android/app/ComponentCaller.java
@@ -18,6 +18,9 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
 import android.os.IBinder;
 import android.os.Process;
 
@@ -118,6 +121,40 @@
         return ActivityClient.getInstance().getLaunchedFromPackage(mActivityToken);
     }
 
+    /**
+     * Determines whether this component caller had access to a specific content URI at launch time.
+     * Apps can use this API to validate content URIs coming from other apps.
+     *
+     * <p><b>Note</b>, in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only
+     * {@link Activity} has access to {@link ComponentCaller} instances.
+     *
+     * <p>Before using this method, note the following:
+     * <ul>
+     *     <li>You must have access to the supplied URI, otherwise it will throw a
+     *     {@link SecurityException}.
+     *     <li>This is not a real time check, i.e. the permissions have been computed at launch
+     *     time.
+     *     <li>This method will return the correct result for content URIs passed at launch time,
+     *     specifically the ones from {@link Intent#getData()}, and {@link Intent#getClipData()} in
+     *     the intent of {@code startActivity(intent)}. For others, it will throw an
+     *     {@link IllegalArgumentException}.
+     * </ul>
+     *
+     * @param uri The content uri that is being checked
+     * @param modeFlags The access modes to check
+     * @return {@link PackageManager#PERMISSION_GRANTED} if this activity caller is allowed to
+     *         access that uri, or {@link PackageManager#PERMISSION_DENIED} if it is not
+     * @throws IllegalArgumentException if uri is a non-content URI or it wasn't passed at launch
+     * @throws SecurityException if you don't have access to uri
+     *
+     * @see android.content.Context#checkContentUriPermissionFull(Uri, int, int, int)
+     */
+    @PackageManager.PermissionResult
+    public int checkContentUriPermission(@NonNull Uri uri, @Intent.AccessUriMode int modeFlags) {
+        return ActivityClient.getInstance().checkActivityCallerContentUriPermission(mActivityToken,
+                mCallerToken, uri, modeFlags);
+    }
+
     @Override
     public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj instanceof ComponentCaller other)) {
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 5b044f6..05fee72 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -23,6 +23,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.IRemoteCallback;
 import android.os.PersistableBundle;
@@ -91,6 +92,9 @@
     int getLaunchedFromUid(in IBinder token);
     String getLaunchedFromPackage(in IBinder token);
 
+    int checkActivityCallerContentUriPermission(in IBinder activityToken, in IBinder callerToken,
+            in Uri uri, int modeFlags, int userId);
+
     void setRequestedOrientation(in IBinder token, int requestedOrientation);
     int getRequestedOrientation(in IBinder token);
 
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 366b45b..8c886fe 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -379,6 +379,27 @@
             = "android.app.action.NOTIFICATION_POLICY_CHANGED";
 
     /**
+     * Intent that is broadcast when the state of {@link #getConsolidatedNotificationPolicy()}
+     * changes.
+     *
+     * <p>This broadcast is only sent to registered receivers and receivers in packages that have
+     * been granted Do Not Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED =
+            "android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED";
+
+    /**
+     * Extra for {@link #ACTION_NOTIFICATION_POLICY_CHANGED} and
+     * {@link #ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED} containing the new
+     * {@link Policy} value.
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final String EXTRA_NOTIFICATION_POLICY =
+            "android.app.extra.NOTIFICATION_POLICY";
+
+    /**
      * Intent that is broadcast when the state of getCurrentInterruptionFilter() changes.
      *
      * <p>This broadcast is only sent to registered receivers and (starting from
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 729f92a..f92ff83 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -55,6 +55,9 @@
 per-file Broadcast* = file:/BROADCASTS_OWNERS
 per-file ReceiverInfo* = file:/BROADCASTS_OWNERS
 
+# ComponentCaller
+per-file ComponentCaller.java = file:COMPONENT_CALLER_OWNERS
+
 # GrammaticalInflectionManager
 per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
 per-file grammatical_inflection_manager.aconfig = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index 077f7b5..3b281e9 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -206,9 +206,10 @@
      * and kill the WearableSensingService process.
      *
      * <p>Before providing the secureWearableConnection, the system will restart the
-     * WearableSensingService process. Other method calls into WearableSensingService may be dropped
-     * during the restart. The caller is responsible for ensuring other method calls are queued
-     * until a success status is returned from the {@code statusConsumer}.
+     * WearableSensingService process if it has not been restarted since the last
+     * secureWearableConnection was provided. Other method calls into WearableSensingService may be
+     * dropped during the restart. The caller is responsible for ensuring other method calls are
+     * queued until a success status is returned from the {@code statusConsumer}.
      *
      * @param wearableConnection The connection to provide
      * @param executor Executor on which to run the consumer callback
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d5eee63f..08871d4 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6034,6 +6034,36 @@
             "android.intent.extra.CHOOSER_MODIFY_SHARE_ACTION";
 
     /**
+     * Optional integer extra to be used with {@link #ACTION_CHOOSER} to describe conteng being
+     * shared.
+     * <p>
+     * If provided, sharesheets may customize their UI presentation to include a more precise
+     * description of the content being shared.
+     *
+     * @see #CHOOSER_CONTENT_TYPE_ALBUM
+     * @see #createChooser(Intent, CharSequence)
+     */
+    @FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_ALBUM_TEXT)
+    public static final String EXTRA_CHOOSER_CONTENT_TYPE_HINT =
+            "android.intent.extra.CHOOSER_CONTENT_TYPE_HINT";
+
+    /** @hide */
+    @IntDef(prefix = {"CHOOSER_CONTENT_TYPE_"}, value = {
+            CHOOSER_CONTENT_TYPE_ALBUM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ChooserContentType {}
+
+    /**
+     * Indicates that the content being shared with {@link #ACTION_SEND} represents an album
+     * (e.g. containing photos).
+     *
+     * @see #EXTRA_CHOOSER_CONTENT_TYPE_HINT
+     */
+    @FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_ALBUM_TEXT)
+    public static final int CHOOSER_CONTENT_TYPE_ALBUM = 1;
+
+    /**
      * An {@code ArrayList} of {@code String} annotations describing content for
      * {@link #ACTION_CHOOSER}.
      *
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 79af65a..d913581 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -559,6 +559,10 @@
             sb.append(" sch=");
             sb.append(mDataSchemes.toString());
         }
+        if (Flags.relativeReferenceIntentFilters() && countUriRelativeFilterGroups() > 0) {
+            sb.append(" grp=");
+            sb.append(mUriRelativeFilterGroups.toString());
+        }
         sb.append(" }");
         return sb.toString();
     }
@@ -1807,13 +1811,7 @@
         if (mUriRelativeFilterGroups == null) {
             return false;
         }
-        for (int i = 0; i < mUriRelativeFilterGroups.size(); i++) {
-            UriRelativeFilterGroup group = mUriRelativeFilterGroups.get(i);
-            if (group.matchData(data)) {
-                return group.getAction() == UriRelativeFilterGroup.ACTION_ALLOW;
-            }
-        }
-        return false;
+        return UriRelativeFilterGroup.matchGroupsToUri(mUriRelativeFilterGroups, data);
     }
 
     /**
diff --git a/core/java/android/content/UriRelativeFilter.java b/core/java/android/content/UriRelativeFilter.java
index 9866cd0..6d33246 100644
--- a/core/java/android/content/UriRelativeFilter.java
+++ b/core/java/android/content/UriRelativeFilter.java
@@ -217,6 +217,15 @@
                 + " }";
     }
 
+    /** @hide */
+    public UriRelativeFilterParcel toParcel() {
+        UriRelativeFilterParcel parcel = new UriRelativeFilterParcel();
+        parcel.uriPart = mUriPart;
+        parcel.patternType = mPatternType;
+        parcel.filter = mFilter;
+        return parcel;
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) return true;
@@ -257,4 +266,11 @@
         mPatternType = Integer.parseInt(parser.getAttributeValue(null, PATTERN_STR));
         mFilter = parser.getAttributeValue(null, FILTER_STR);
     }
+
+    /** @hide */
+    public UriRelativeFilter(UriRelativeFilterParcel parcel) {
+        mUriPart = parcel.uriPart;
+        mPatternType = parcel.patternType;
+        mFilter = parcel.filter;
+    }
 }
diff --git a/core/java/android/content/UriRelativeFilterGroup.aidl b/core/java/android/content/UriRelativeFilterGroup.aidl
new file mode 100644
index 0000000..b251054
--- /dev/null
+++ b/core/java/android/content/UriRelativeFilterGroup.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.content;
+
+parcelable UriRelativeFilterGroup;
diff --git a/core/java/android/content/UriRelativeFilterGroup.java b/core/java/android/content/UriRelativeFilterGroup.java
index 72c396a..0e49b4f 100644
--- a/core/java/android/content/UriRelativeFilterGroup.java
+++ b/core/java/android/content/UriRelativeFilterGroup.java
@@ -19,6 +19,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.Flags;
 import android.net.Uri;
 import android.os.Parcel;
@@ -36,9 +37,11 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -83,6 +86,40 @@
     private final @Action int mAction;
     private final ArraySet<UriRelativeFilter> mUriRelativeFilters = new ArraySet<>();
 
+    /** @hide */
+    public static boolean matchGroupsToUri(List<UriRelativeFilterGroup> groups, Uri uri) {
+        for (int i = 0; i < groups.size(); i++) {
+            if (groups.get(i).matchData(uri)) {
+                return groups.get(i).getAction() == UriRelativeFilterGroup.ACTION_ALLOW;
+            }
+        }
+        return false;
+    }
+
+    /** @hide */
+    public static List<UriRelativeFilterGroup> parcelsToGroups(
+            @Nullable List<UriRelativeFilterGroupParcel> parcels) {
+        List<UriRelativeFilterGroup> groups = new ArrayList<>();
+        if (parcels != null) {
+            for (int i = 0; i < parcels.size(); i++) {
+                groups.add(new UriRelativeFilterGroup(parcels.get(i)));
+            }
+        }
+        return groups;
+    }
+
+    /** @hide */
+    public static List<UriRelativeFilterGroupParcel> groupsToParcels(
+            @Nullable List<UriRelativeFilterGroup> groups) {
+        List<UriRelativeFilterGroupParcel> parcels = new ArrayList<>();
+        if (groups != null) {
+            for (int i = 0; i < groups.size(); i++) {
+                parcels.add(groups.get(i).toParcel());
+            }
+        }
+        return parcels;
+    }
+
     /**
      * New UriRelativeFilterGroup that matches a Intent data.
      *
@@ -205,6 +242,35 @@
         }
     }
 
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        UriRelativeFilterGroup that = (UriRelativeFilterGroup) o;
+        if (mAction != that.mAction) return false;
+        return mUriRelativeFilters.equals(that.mUriRelativeFilters);
+    }
+
+    @Override
+    public int hashCode() {
+        int _hash = 0;
+        _hash = 31 * _hash + mAction;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mUriRelativeFilters);
+        return _hash;
+    }
+
+    /** @hide */
+    public UriRelativeFilterGroupParcel toParcel() {
+        UriRelativeFilterGroupParcel parcel = new UriRelativeFilterGroupParcel();
+        parcel.action = mAction;
+        parcel.filters = new ArrayList<>();
+        for (UriRelativeFilter filter : mUriRelativeFilters) {
+            parcel.filters.add(filter.toParcel());
+        }
+        return parcel;
+    }
+
     /** @hide */
     UriRelativeFilterGroup(@NonNull Parcel src) {
         mAction = src.readInt();
@@ -213,4 +279,12 @@
             mUriRelativeFilters.add(new UriRelativeFilter(src));
         }
     }
+
+    /** @hide */
+    public UriRelativeFilterGroup(UriRelativeFilterGroupParcel parcel) {
+        mAction = parcel.action;
+        for (int i = 0; i < parcel.filters.size(); i++) {
+            mUriRelativeFilters.add(new UriRelativeFilter(parcel.filters.get(i)));
+        }
+    }
 }
diff --git a/core/java/android/content/UriRelativeFilterGroupParcel.aidl b/core/java/android/content/UriRelativeFilterGroupParcel.aidl
new file mode 100644
index 0000000..3679e7f
--- /dev/null
+++ b/core/java/android/content/UriRelativeFilterGroupParcel.aidl
@@ -0,0 +1,28 @@
+/**
+ * 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.content;
+
+import android.content.UriRelativeFilterParcel;
+
+/**
+ * Class for holding UriRelativeFilterGroup data.
+ * @hide
+ */
+parcelable UriRelativeFilterGroupParcel {
+    int action;
+    List<UriRelativeFilterParcel> filters;
+}
diff --git a/core/java/android/content/UriRelativeFilterParcel.aidl b/core/java/android/content/UriRelativeFilterParcel.aidl
new file mode 100644
index 0000000..4fb196d
--- /dev/null
+++ b/core/java/android/content/UriRelativeFilterParcel.aidl
@@ -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 android.content;
+
+/**
+ * Class for holding UriRelativeFilter data.
+ * @hide
+ */
+parcelable UriRelativeFilterParcel {
+    int uriPart;
+    int patternType;
+    String filter;
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c2ff9f6..17e6f16 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -3185,13 +3185,14 @@
         }
 
         /**
-         * If rollback enabled for this session (via {@link #setEnableRollback}, set time
-         * after which rollback will no longer be possible
+         * If rollback enabled for this session (via {@link #setEnableRollback}, set period
+         * after which rollback files will be deleted due to expiration
+         * {@link RollbackManagerServiceImpl#deleteRollback}.
          *
          * <p>For multi-package installs, this value must be set on the parent session.
          * Child session rollback lifetime will be ignored.
          *
-         * @param lifetimeMillis time after which rollback expires
+         * @param lifetimeMillis period after which rollback expires
          * @throws IllegalArgumentException if lifetimeMillis is negative or rollback is not
          * enabled via setEnableRollback.
          * @hide
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index 77bd147..4dcc517 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -17,6 +17,7 @@
 package android.content.pm.verify.domain;
 
 import android.annotation.CheckResult;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -25,15 +26,21 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.Intent;
+import android.content.UriRelativeFilterGroup;
+import android.content.UriRelativeFilterGroupParcel;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 
 import com.android.internal.util.CollectionUtils;
 
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
@@ -156,6 +163,74 @@
     }
 
     /**
+     * Update the URI relative filter groups for a package. All previously existing groups
+     * will be cleared before the new groups will be applied.
+     *
+     * @param packageName The name of the package.
+     * @param domainToGroupsMap A map of domains to a list of {@link UriRelativeFilterGroup}s that
+     *                         should apply to them. Groups for each domain will replace any groups
+     *                         provided for that domain in a prior call to this method. Groups will
+     *                         be evaluated in the order they are provided.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+    @FlaggedApi(android.content.pm.Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+    public void setUriRelativeFilterGroups(@NonNull String packageName,
+            @NonNull Map<String, List<UriRelativeFilterGroup>> domainToGroupsMap) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(domainToGroupsMap);
+        Bundle bundle = new Bundle();
+        for (String domain : domainToGroupsMap.keySet()) {
+            List<UriRelativeFilterGroup> groups = domainToGroupsMap.get(domain);
+            bundle.putParcelableList(domain, UriRelativeFilterGroup.groupsToParcels(groups));
+        }
+        try {
+            mDomainVerificationManager.setUriRelativeFilterGroups(packageName, bundle);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieves a map of a package's verified domains to a list of {@link UriRelativeFilterGroup}s
+     * that applies to them.
+     *
+     * @param packageName The name of the package.
+     * @param domains List of domains for which to retrieve group matches.
+     * @return A map of domains to the lists of {@link UriRelativeFilterGroup}s that apply to them.
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @FlaggedApi(android.content.pm.Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+    public Map<String, List<UriRelativeFilterGroup>> getUriRelativeFilterGroups(
+            @NonNull String packageName,
+            @NonNull List<String> domains) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(domains);
+        if (domains.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        try {
+            Bundle bundle = mDomainVerificationManager.getUriRelativeFilterGroups(packageName,
+                    domains);
+            ArrayMap<String, List<UriRelativeFilterGroup>> map = new ArrayMap<>();
+            if (!bundle.isEmpty()) {
+                for (String domain : bundle.keySet()) {
+                    List<UriRelativeFilterGroupParcel> parcels =
+                            bundle.getParcelableArrayList(domain,
+                                    UriRelativeFilterGroupParcel.class);
+                    map.put(domain, UriRelativeFilterGroup.parcelsToGroups(parcels));
+                }
+            }
+            return map;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is
      * usually a heavy workload and should be done infrequently.
      *
diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
index 53205f3..f5af82d 100644
--- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
+++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
@@ -20,6 +20,8 @@
 import android.content.pm.verify.domain.DomainSet;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationUserState;
+import android.content.UriRelativeFilterGroup;
+import android.os.Bundle;
 import java.util.List;
 
 /**
@@ -46,4 +48,8 @@
 
     int setDomainVerificationUserSelection(String domainSetId, in DomainSet domains,
             boolean enabled, int userId);
+
+    void setUriRelativeFilterGroups(String packageName, in Bundle domainToGroupsBundle);
+
+    Bundle getUriRelativeFilterGroups(String packageName, in List<String> domains);
 }
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 3671980..7fba3e8 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -89,7 +89,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.WeakHashMap;
 
@@ -188,7 +187,7 @@
     private int mBaseApkAssetsSize;
 
     /** @hide */
-    private static Set<Resources> sResourcesHistory = Collections.synchronizedSet(
+    private static final Set<Resources> sResourcesHistory = Collections.synchronizedSet(
             Collections.newSetFromMap(
                     new WeakHashMap<>()));
 
@@ -2808,7 +2807,12 @@
     public void dump(PrintWriter pw, String prefix) {
         pw.println(prefix + "class=" + getClass());
         pw.println(prefix + "resourcesImpl");
-        mResourcesImpl.dump(pw, prefix + "  ");
+        final var impl = mResourcesImpl;
+        if (impl != null) {
+            impl.dump(pw, prefix + "  ");
+        } else {
+            pw.println(prefix + "  " + "null");
+        }
     }
 
     /** @hide */
@@ -2816,15 +2820,22 @@
         pw.println(prefix + "history");
         // Putting into a map keyed on the apk assets to deduplicate resources that are different
         // objects but ultimately represent the same assets
-        Map<List<ApkAssets>, Resources> history = new ArrayMap<>();
+        ArrayMap<List<ApkAssets>, Resources> history = new ArrayMap<>();
         sResourcesHistory.forEach(
-                r -> history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r));
+                r -> {
+                    if (r != null) {
+                        final var impl = r.mResourcesImpl;
+                        if (impl != null) {
+                            history.put(Arrays.asList(impl.mAssets.getApkAssets()), r);
+                        } else {
+                            history.put(null, r);
+                        }
+                    }
+                });
         int i = 0;
         for (Resources r : history.values()) {
-            if (r != null) {
-                pw.println(prefix + i++);
-                r.dump(pw, prefix + "  ");
-            }
+            pw.println(prefix + i++);
+            r.dump(pw, prefix + "  ");
         }
     }
 
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index ec46d2f..ef7b259 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -62,3 +62,10 @@
     description: "Enables Credential Manager on Wear Platform"
     bug: "301168341"
 }
+
+flag {
+    namespace: "credential_manager"
+    name: "new_framework_metrics"
+    description: "Enables new metrics fror 24Q3 / VIC"
+    bug: "324291187"
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 7abe821..3b10e0d 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -19,7 +19,9 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
@@ -40,6 +42,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
@@ -53,6 +56,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -243,16 +247,19 @@
         private static final String PROXY_SERVICE_NAME =
                 "com.android.cameraextensions.CameraExtensionsProxyService";
 
+        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+        private static final int FALLBACK_PACKAGE_NAME =
+                com.android.internal.R.string.config_extensionFallbackPackageName;
+        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+        private static final int FALLBACK_SERVICE_NAME =
+                com.android.internal.R.string.config_extensionFallbackServiceName;
+
         // Singleton instance
         private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER =
                 new CameraExtensionManagerGlobal();
         private final Object mLock = new Object();
         private final int PROXY_SERVICE_DELAY_MS = 2000;
-        private InitializerFuture mInitFuture = null;
-        private ServiceConnection mConnection = null;
-        private int mConnectionCount = 0;
-        private ICameraExtensionsProxyService mProxy = null;
-        private boolean mSupportsAdvancedExtensions = false;
+        private ExtensionConnectionManager mConnectionManager = new ExtensionConnectionManager();
 
         // Singleton, don't allow construction
         private CameraExtensionManagerGlobal() {}
@@ -261,17 +268,17 @@
             return GLOBAL_CAMERA_MANAGER;
         }
 
-        private void releaseProxyConnectionLocked(Context ctx) {
-            if (mConnection != null ) {
-                ctx.unbindService(mConnection);
-                mConnection = null;
-                mProxy = null;
-                mConnectionCount = 0;
+        private void releaseProxyConnectionLocked(Context ctx, int extension) {
+            if (mConnectionManager.getConnection(extension) != null) {
+                ctx.unbindService(mConnectionManager.getConnection(extension));
+                mConnectionManager.setConnection(extension, null);
+                mConnectionManager.setProxy(extension, null);
+                mConnectionManager.resetConnectionCount(extension);
             }
         }
 
-        private void connectToProxyLocked(Context ctx) {
-            if (mConnection == null) {
+        private void connectToProxyLocked(Context ctx, int extension, boolean useFallback) {
+            if (mConnectionManager.getConnection(extension) == null) {
                 Intent intent = new Intent();
                 intent.setClassName(PROXY_PACKAGE_NAME, PROXY_SERVICE_NAME);
                 String vendorProxyPackage = SystemProperties.get(
@@ -287,34 +294,55 @@
                       + vendorProxyService);
                   intent.setClassName(vendorProxyPackage, vendorProxyService);
                 }
-                mInitFuture = new InitializerFuture();
-                mConnection = new ServiceConnection() {
+
+                if (Flags.concertMode() && useFallback) {
+                    String packageName = ctx.getResources().getString(FALLBACK_PACKAGE_NAME);
+                    String serviceName = ctx.getResources().getString(FALLBACK_SERVICE_NAME);
+
+                    if (!packageName.isEmpty() && !serviceName.isEmpty()) {
+                        Log.v(TAG,
+                                "Choosing the fallback software implementation package: "
+                                + packageName);
+                        Log.v(TAG,
+                                "Choosing the fallback software implementation service: "
+                                + serviceName);
+                        intent.setClassName(packageName, serviceName);
+                    }
+                }
+
+                InitializerFuture initFuture = new InitializerFuture();
+                ServiceConnection connection = new ServiceConnection() {
                     @Override
                     public void onServiceDisconnected(ComponentName component) {
-                        mConnection = null;
-                        mProxy = null;
+                        mConnectionManager.setConnection(extension, null);
+                        mConnectionManager.setProxy(extension, null);
                     }
 
                     @Override
                     public void onServiceConnected(ComponentName component, IBinder binder) {
-                        mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder);
-                        if (mProxy == null) {
+                        ICameraExtensionsProxyService proxy =
+                                ICameraExtensionsProxyService.Stub.asInterface(binder);
+                        mConnectionManager.setProxy(extension, proxy);
+                        if (mConnectionManager.getProxy(extension) == null) {
                             throw new IllegalStateException("Camera Proxy service is null");
                         }
                         try {
-                            mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported();
+                            mConnectionManager.setAdvancedExtensionsSupported(extension,
+                                    mConnectionManager.getProxy(extension)
+                                    .advancedExtensionsSupported());
                         } catch (RemoteException e) {
                             Log.e(TAG, "Remote IPC failed!");
                         }
-                        mInitFuture.setStatus(true);
+                        initFuture.setStatus(true);
                     }
                 };
                 ctx.bindService(intent, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT |
                         Context.BIND_ABOVE_CLIENT | Context.BIND_NOT_VISIBLE,
-                        android.os.AsyncTask.THREAD_POOL_EXECUTOR, mConnection);
+                        android.os.AsyncTask.THREAD_POOL_EXECUTOR, connection);
+                mConnectionManager.setConnection(extension, connection);
 
                 try {
-                    mInitFuture.get(PROXY_SERVICE_DELAY_MS, TimeUnit.MILLISECONDS);
+                    initFuture.get(PROXY_SERVICE_DELAY_MS, TimeUnit.MILLISECONDS);
                 } catch (TimeoutException e) {
                     Log.e(TAG, "Timed out while initializing proxy service!");
                 }
@@ -366,64 +394,102 @@
             }
         }
 
-        public boolean registerClient(Context ctx, IBinder token) {
+        public boolean registerClientHelper(Context ctx, IBinder token, int extension,
+                boolean useFallback) {
             synchronized (mLock) {
                 boolean ret = false;
-                connectToProxyLocked(ctx);
-                if (mProxy == null) {
+                connectToProxyLocked(ctx, extension, useFallback);
+                if (mConnectionManager.getProxy(extension) == null) {
                     return false;
                 }
-                mConnectionCount++;
+                mConnectionManager.incrementConnectionCount(extension);
 
                 try {
-                    ret = mProxy.registerClient(token);
+                    ret = mConnectionManager.getProxy(extension).registerClient(token);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to initialize extension! Extension service does "
                             + " not respond!");
                 }
                 if (!ret) {
-                    mConnectionCount--;
+                    mConnectionManager.decrementConnectionCount(extension);
                 }
 
-                if (mConnectionCount <= 0) {
-                    releaseProxyConnectionLocked(ctx);
+                if (mConnectionManager.getConnectionCount(extension) <= 0) {
+                    releaseProxyConnectionLocked(ctx, extension);
                 }
 
                 return ret;
             }
         }
 
-        public void unregisterClient(Context ctx, IBinder token) {
+        @SuppressLint("NonUserGetterCalled")
+        public boolean registerClient(Context ctx, IBinder token, int extension,
+                String cameraId, Map<String, CameraMetadataNative> characteristicsMapNative) {
+            boolean ret = registerClientHelper(ctx, token, extension, false /*useFallback*/);
+
+            if (Flags.concertMode()) {
+                // Check if user enabled fallback impl
+                ContentResolver resolver = ctx.getContentResolver();
+                int userEnabled = Settings.Secure.getInt(resolver,
+                        Settings.Secure.CAMERA_EXTENSIONS_FALLBACK, 1);
+
+                boolean vendorImpl = true;
+                if (ret && (mConnectionManager.getProxy(extension) != null) && (userEnabled == 1)) {
+                    // At this point, we are connected to either CameraExtensionsProxyService or
+                    // the vendor extension proxy service. If the vendor does not support the
+                    // extension, unregisterClient and re-register client with the proxy service
+                    // containing the fallback impl
+                    vendorImpl = isExtensionSupported(cameraId, extension,
+                            characteristicsMapNative);
+                }
+
+                if (!vendorImpl) {
+                    unregisterClient(ctx, token, extension);
+                    ret = registerClientHelper(ctx, token, extension, true /*useFallback*/);
+
+                }
+            }
+
+            return ret;
+        }
+
+        public void unregisterClient(Context ctx, IBinder token, int extension) {
             synchronized (mLock) {
-                if (mProxy != null) {
+                if (mConnectionManager.getProxy(extension) != null) {
                     try {
-                        mProxy.unregisterClient(token);
+                        mConnectionManager.getProxy(extension).unregisterClient(token);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Failed to de-initialize extension! Extension service does"
                                 + " not respond!");
                     } finally {
-                        mConnectionCount--;
-                        if (mConnectionCount <= 0) {
-                            releaseProxyConnectionLocked(ctx);
+                        mConnectionManager.decrementConnectionCount(extension);
+                        if (mConnectionManager.getConnectionCount(extension) <= 0) {
+                            releaseProxyConnectionLocked(ctx, extension);
                         }
                     }
                 }
             }
         }
 
-        public void initializeSession(IInitializeSessionCallback cb) throws RemoteException {
+        public void initializeSession(IInitializeSessionCallback cb, int extension)
+                throws RemoteException {
             synchronized (mLock) {
-                if (mProxy != null) {
-                    mProxy.initializeSession(cb);
+                if (mConnectionManager.getProxy(extension) != null
+                        && !mConnectionManager.isSessionInitialized()) {
+                    mConnectionManager.getProxy(extension).initializeSession(cb);
+                    mConnectionManager.setSessionInitialized(true);
+                } else {
+                    cb.onFailure();
                 }
             }
         }
 
-        public void releaseSession() {
+        public void releaseSession(int extension) {
             synchronized (mLock) {
-                if (mProxy != null) {
+                if (mConnectionManager.getProxy(extension) != null) {
                     try {
-                        mProxy.releaseSession();
+                        mConnectionManager.getProxy(extension).releaseSession();
+                        mConnectionManager.setSessionInitialized(false);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Failed to release session! Extension service does"
                                 + " not respond!");
@@ -432,77 +498,157 @@
             }
         }
 
-        public boolean areAdvancedExtensionsSupported() {
-            return mSupportsAdvancedExtensions;
+        public boolean areAdvancedExtensionsSupported(int extension) {
+            return mConnectionManager.areAdvancedExtensionsSupported(extension);
         }
 
-        public IPreviewExtenderImpl initializePreviewExtension(int extensionType)
+        public IPreviewExtenderImpl initializePreviewExtension(int extension)
                 throws RemoteException {
             synchronized (mLock) {
-                if (mProxy != null) {
-                    return mProxy.initializePreviewExtension(extensionType);
+                if (mConnectionManager.getProxy(extension) != null) {
+                    return mConnectionManager.getProxy(extension)
+                            .initializePreviewExtension(extension);
                 } else {
                     return null;
                 }
             }
         }
 
-        public IImageCaptureExtenderImpl initializeImageExtension(int extensionType)
+        public IImageCaptureExtenderImpl initializeImageExtension(int extension)
                 throws RemoteException {
             synchronized (mLock) {
-                if (mProxy != null) {
-                    return mProxy.initializeImageExtension(extensionType);
+                if (mConnectionManager.getProxy(extension) != null) {
+                    return mConnectionManager.getProxy(extension)
+                            .initializeImageExtension(extension);
                 } else {
                     return null;
                 }
             }
         }
 
-        public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType)
+        public IAdvancedExtenderImpl initializeAdvancedExtension(int extension)
                 throws RemoteException {
             synchronized (mLock) {
-                if (mProxy != null) {
-                    return mProxy.initializeAdvancedExtension(extensionType);
+                if (mConnectionManager.getProxy(extension) != null) {
+                    return mConnectionManager.getProxy(extension)
+                            .initializeAdvancedExtension(extension);
                 } else {
                     return null;
                 }
             }
         }
+
+        private class ExtensionConnectionManager {
+            // Maps extension to ExtensionConnection
+            private Map<Integer, ExtensionConnection> mConnections = new HashMap<>();
+            private boolean mSessionInitialized = false;
+
+            public ExtensionConnectionManager() {
+                IntArray extensionList = new IntArray(EXTENSION_LIST.length);
+                extensionList.addAll(EXTENSION_LIST);
+                if (Flags.concertMode()) {
+                    extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
+                }
+
+                for (int extensionType : extensionList.toArray()) {
+                    mConnections.put(extensionType, new ExtensionConnection());
+                }
+            }
+
+            public ICameraExtensionsProxyService getProxy(@Extension int extension) {
+                return mConnections.get(extension).mProxy;
+            }
+
+            public ServiceConnection getConnection(@Extension int extension) {
+                return mConnections.get(extension).mConnection;
+            }
+
+            public int getConnectionCount(@Extension int extension) {
+                return mConnections.get(extension).mConnectionCount;
+            }
+
+            public boolean areAdvancedExtensionsSupported(@Extension int extension) {
+                return mConnections.get(extension).mSupportsAdvancedExtensions;
+            }
+
+            public boolean isSessionInitialized() {
+                return mSessionInitialized;
+            }
+
+            public void setProxy(@Extension int extension, ICameraExtensionsProxyService proxy) {
+                mConnections.get(extension).mProxy = proxy;
+            }
+
+            public void setConnection(@Extension int extension, ServiceConnection connection) {
+                mConnections.get(extension).mConnection = connection;
+            }
+
+            public void incrementConnectionCount(@Extension int extension) {
+                mConnections.get(extension).mConnectionCount++;
+            }
+
+            public void decrementConnectionCount(@Extension int extension) {
+                mConnections.get(extension).mConnectionCount--;
+            }
+
+            public void resetConnectionCount(@Extension int extension) {
+                mConnections.get(extension).mConnectionCount = 0;
+            }
+
+            public void setAdvancedExtensionsSupported(@Extension int extension,
+                    boolean advancedExtSupported) {
+                mConnections.get(extension).mSupportsAdvancedExtensions = advancedExtSupported;
+            }
+
+            public void setSessionInitialized(boolean initialized) {
+                mSessionInitialized = initialized;
+            }
+
+            private class ExtensionConnection {
+                public ICameraExtensionsProxyService mProxy = null;
+                public ServiceConnection mConnection = null;
+                public int mConnectionCount = 0;
+                public boolean mSupportsAdvancedExtensions = false;
+            }
+        }
     }
 
     /**
      * @hide
      */
-    public static boolean registerClient(Context ctx, IBinder token) {
-        return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
+    public static boolean registerClient(Context ctx, IBinder token, int extension,
+            String cameraId, Map<String, CameraMetadataNative> characteristicsMapNative) {
+        return CameraExtensionManagerGlobal.get().registerClient(ctx, token, extension, cameraId,
+                characteristicsMapNative);
     }
 
     /**
      * @hide
      */
-    public static void unregisterClient(Context ctx, IBinder token) {
-        CameraExtensionManagerGlobal.get().unregisterClient(ctx, token);
+    public static void unregisterClient(Context ctx, IBinder token, int extension) {
+        CameraExtensionManagerGlobal.get().unregisterClient(ctx, token, extension);
     }
 
     /**
      * @hide
      */
-    public static void initializeSession(IInitializeSessionCallback cb) throws RemoteException {
-        CameraExtensionManagerGlobal.get().initializeSession(cb);
+    public static void initializeSession(IInitializeSessionCallback cb, int extension)
+            throws RemoteException {
+        CameraExtensionManagerGlobal.get().initializeSession(cb, extension);
     }
 
     /**
      * @hide
      */
-    public static void releaseSession() {
-        CameraExtensionManagerGlobal.get().releaseSession();
+    public static void releaseSession(int extension) {
+        CameraExtensionManagerGlobal.get().releaseSession(extension);
     }
 
     /**
      * @hide
      */
-    public static boolean areAdvancedExtensionsSupported() {
-        return CameraExtensionManagerGlobal.get().areAdvancedExtensionsSupported();
+    public static boolean areAdvancedExtensionsSupported(int extension) {
+        return CameraExtensionManagerGlobal.get().areAdvancedExtensionsSupported(extension);
     }
 
     /**
@@ -510,7 +656,7 @@
      */
     public static boolean isExtensionSupported(String cameraId, int extensionType,
             Map<String, CameraMetadataNative> characteristicsMap) {
-        if (areAdvancedExtensionsSupported()) {
+        if (areAdvancedExtensionsSupported(extensionType)) {
             try {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extensionType);
                 return extender.isExtensionAvailable(cameraId, characteristicsMap);
@@ -600,24 +746,24 @@
     public @NonNull List<Integer> getSupportedExtensions() {
         ArrayList<Integer> ret = new ArrayList<>();
         final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId);
-        boolean success = registerClient(mContext, token);
-        if (!success) {
-            return Collections.unmodifiableList(ret);
-        }
 
         IntArray extensionList = new IntArray(EXTENSION_LIST.length);
         extensionList.addAll(EXTENSION_LIST);
         if (Flags.concertMode()) {
             extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
         }
-        try {
-            for (int extensionType : extensionList.toArray()) {
-                if (isExtensionSupported(mCameraId, extensionType, mCharacteristicsMapNative)) {
+
+        for (int extensionType : extensionList.toArray()) {
+            try {
+                boolean success = registerClient(mContext, token, extensionType, mCameraId,
+                        mCharacteristicsMapNative);
+                if (success && isExtensionSupported(mCameraId, extensionType,
+                        mCharacteristicsMapNative)) {
                     ret.add(extensionType);
                 }
+            } finally {
+                unregisterClient(mContext, token, extensionType);
             }
-        } finally {
-            unregisterClient(mContext, token);
         }
 
         return Collections.unmodifiableList(ret);
@@ -643,7 +789,8 @@
     public <T> @Nullable T get(@Extension int extension,
             @NonNull CameraCharacteristics.Key<T> key) {
         final IBinder token = new Binder(TAG + "#get:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -653,7 +800,7 @@
                 throw new IllegalArgumentException("Unsupported extension");
             }
 
-            if (areAdvancedExtensionsSupported() && getKeys(extension).contains(key)) {
+            if (areAdvancedExtensionsSupported(extension) && getKeys(extension).contains(key)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 CameraMetadataNative metadata =
@@ -670,7 +817,7 @@
             Log.e(TAG, "Failed to query the extension for the specified key! Extension "
                     + "service does not respond!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
         return null;
     }
@@ -691,7 +838,8 @@
     public @NonNull Set<CameraCharacteristics.Key> getKeys(@Extension int extension) {
         final IBinder token =
                 new Binder(TAG + "#getKeys:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -703,7 +851,7 @@
                 throw new IllegalArgumentException("Unsupported extension");
             }
 
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 CameraMetadataNative metadata =
@@ -732,7 +880,7 @@
             Log.e(TAG, "Failed to query the extension for all available keys! Extension "
                     + "service does not respond!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
         return Collections.unmodifiableSet(ret);
     }
@@ -755,7 +903,8 @@
      */
     public boolean isPostviewAvailable(@Extension int extension) {
         final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -765,7 +914,7 @@
                 throw new IllegalArgumentException("Unsupported extension");
             }
 
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 return extender.isPostviewAvailable();
@@ -779,7 +928,7 @@
             Log.e(TAG, "Failed to query the extension for postview availability! Extension "
                     + "service does not respond!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
 
         return false;
@@ -813,7 +962,8 @@
     public List<Size> getPostviewSupportedSizes(@Extension int extension,
             @NonNull Size captureSize, int format) {
         final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -831,7 +981,7 @@
             StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
 
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 switch(format) {
                     case ImageFormat.YUV_420_888:
                     case ImageFormat.JPEG:
@@ -879,7 +1029,7 @@
                     + "service does not respond!");
             return Collections.emptyList();
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
     }
 
@@ -917,7 +1067,8 @@
         //       ambiguity is resolved in b/169799538.
 
         final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -929,7 +1080,7 @@
 
             StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 return generateSupportedSizes(
@@ -948,7 +1099,7 @@
                     + " not respond!");
             return new ArrayList<>();
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
     }
 
@@ -978,7 +1129,8 @@
     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
         try {
             final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
-            boolean success = registerClient(mContext, token);
+            boolean success = registerClient(mContext, token, extension, mCameraId,
+                    mCharacteristicsMapNative);
             if (!success) {
                 throw new IllegalArgumentException("Unsupported extensions");
             }
@@ -990,7 +1142,7 @@
 
                 StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
                         CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-                if (areAdvancedExtensionsSupported()) {
+                if (areAdvancedExtensionsSupported(extension)) {
                     switch(format) {
                         case ImageFormat.YUV_420_888:
                         case ImageFormat.JPEG:
@@ -1035,7 +1187,7 @@
                     }
                 }
             } finally {
-                unregisterClient(mContext, token);
+                unregisterClient(mContext, token, extension);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
@@ -1073,7 +1225,8 @@
         }
 
         final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -1087,7 +1240,7 @@
                     new android.hardware.camera2.extension.Size();
             sz.width = captureOutputSize.getWidth();
             sz.height = captureOutputSize.getHeight();
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 LatencyRange latencyRange = extender.getEstimatedCaptureLatencyRange(mCameraId,
@@ -1126,7 +1279,7 @@
             Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
                     + " not respond!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
 
         return null;
@@ -1143,7 +1296,8 @@
      */
     public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
         final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -1153,7 +1307,7 @@
                 throw new IllegalArgumentException("Unsupported extension");
             }
 
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 return extender.isCaptureProcessProgressAvailable();
@@ -1167,7 +1321,7 @@
             Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
                     + " not respond!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
 
         return false;
@@ -1195,7 +1349,8 @@
     @NonNull
     public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) {
         final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -1208,7 +1363,7 @@
             }
 
             CameraMetadataNative captureRequestMeta = null;
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 captureRequestMeta = extender.getAvailableCaptureRequestKeys(mCameraId);
@@ -1250,7 +1405,7 @@
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to query the available capture request keys!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
 
         return Collections.unmodifiableSet(ret);
@@ -1282,7 +1437,8 @@
     @NonNull
     public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) {
         final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId);
-        boolean success = registerClient(mContext, token);
+        boolean success = registerClient(mContext, token, extension, mCameraId,
+                mCharacteristicsMapNative);
         if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
@@ -1294,7 +1450,7 @@
             }
 
             CameraMetadataNative captureResultMeta = null;
-            if (areAdvancedExtensionsSupported()) {
+            if (areAdvancedExtensionsSupported(extension)) {
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
                 captureResultMeta = extender.getAvailableCaptureResultKeys(mCameraId);
@@ -1336,7 +1492,7 @@
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to query the available capture result keys!");
         } finally {
-            unregisterClient(mContext, token);
+            unregisterClient(mContext, token, extension);
         }
 
         return Collections.unmodifiableSet(ret);
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index f6c8f36..b2032fa 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -102,6 +102,8 @@
 
     private boolean mInitialized;
     private boolean mSessionClosed;
+    private int mExtensionType;
+
 
     private final Context mContext;
 
@@ -205,7 +207,7 @@
         CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx,
                 extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
                 burstCaptureSurface, postviewSurface, config.getStateCallback(),
-                config.getExecutor(), sessionId, token);
+                config.getExecutor(), sessionId, token, config.getExtension());
 
         ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
         ret.mStatsAggregator.setExtensionType(config.getExtension());
@@ -223,7 +225,8 @@
             @Nullable Surface postviewSurface,
             @NonNull StateCallback callback, @NonNull Executor executor,
             int sessionId,
-            @NonNull IBinder token) {
+            @NonNull IBinder token,
+            int extension) {
         mContext = ctx;
         mAdvancedExtender = extender;
         mCameraDevice = cameraDevice;
@@ -242,6 +245,7 @@
         mSessionId = sessionId;
         mToken = token;
         mInterfaceLock = cameraDevice.mInterfaceLock;
+        mExtensionType = extension;
 
         mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(),
                 /*isAdvanced=*/true);
@@ -583,9 +587,9 @@
             if (mToken != null) {
                 if (mInitialized || (mCaptureSession != null)) {
                     notifyClose = true;
-                    CameraExtensionCharacteristics.releaseSession();
+                    CameraExtensionCharacteristics.releaseSession(mExtensionType);
                 }
-                CameraExtensionCharacteristics.unregisterClient(mContext, mToken);
+                CameraExtensionCharacteristics.unregisterClient(mContext, mToken, mExtensionType);
             }
             mInitialized = false;
             mToken = null;
@@ -654,7 +658,8 @@
             }
 
             try {
-                CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
+                CameraExtensionCharacteristics.initializeSession(
+                        mInitializeHandler, mExtensionType);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to initialize session! Extension service does"
                         + " not respond!");
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index ccb24e7..f03876b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -2559,13 +2559,16 @@
         boolean initializationFailed = true;
         IBinder token = new Binder(TAG + " : " + mNextSessionId++);
         try {
-            boolean ret = CameraExtensionCharacteristics.registerClient(mContext, token);
+            boolean ret = CameraExtensionCharacteristics.registerClient(mContext, token,
+                    extensionConfiguration.getExtension(), mCameraId,
+                    CameraExtensionUtils.getCharacteristicsMapNative(characteristicsMap));
             if (!ret) {
                 token = null;
                 throw new UnsupportedOperationException("Unsupported extension!");
             }
 
-            if (CameraExtensionCharacteristics.areAdvancedExtensionsSupported()) {
+            if (CameraExtensionCharacteristics.areAdvancedExtensionsSupported(
+                        extensionConfiguration.getExtension())) {
                 mCurrentAdvancedExtensionSession =
                         CameraAdvancedExtensionSessionImpl.createCameraAdvancedExtensionSession(
                                 this, characteristicsMap, mContext, extensionConfiguration,
@@ -2580,7 +2583,8 @@
             throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
         } finally {
             if (initializationFailed && (token != null)) {
-                CameraExtensionCharacteristics.unregisterClient(mContext, token);
+                CameraExtensionCharacteristics.unregisterClient(mContext, token,
+                        extensionConfiguration.getExtension());
             }
         }
     }
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index db7055b..725b413 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -118,6 +118,7 @@
     // In case the client doesn't explicitly enable repeating requests, the framework
     // will do so internally.
     private boolean mInternalRepeatingRequestEnabled = true;
+    private int mExtensionType;
 
     private final Context mContext;
 
@@ -244,7 +245,8 @@
                 sessionId,
                 token,
                 extensionChars.getAvailableCaptureRequestKeys(config.getExtension()),
-                extensionChars.getAvailableCaptureResultKeys(config.getExtension()));
+                extensionChars.getAvailableCaptureResultKeys(config.getExtension()),
+                config.getExtension());
 
         session.mStatsAggregator.setClientName(ctx.getOpPackageName());
         session.mStatsAggregator.setExtensionType(config.getExtension());
@@ -266,7 +268,8 @@
             int sessionId,
             @NonNull IBinder token,
             @NonNull Set<CaptureRequest.Key> requestKeys,
-            @Nullable Set<CaptureResult.Key> resultKeys) {
+            @Nullable Set<CaptureResult.Key> resultKeys,
+            int extension) {
         mContext = ctx;
         mImageExtender = imageExtender;
         mPreviewExtender = previewExtender;
@@ -289,6 +292,7 @@
         mSupportedResultKeys = resultKeys;
         mCaptureResultsSupported = !resultKeys.isEmpty();
         mInterfaceLock = cameraDevice.mInterfaceLock;
+        mExtensionType = extension;
 
         mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(),
                 /*isAdvanced=*/false);
@@ -881,9 +885,9 @@
             if (mToken != null) {
                 if (mInitialized || (mCaptureSession != null)) {
                     notifyClose = true;
-                    CameraExtensionCharacteristics.releaseSession();
+                    CameraExtensionCharacteristics.releaseSession(mExtensionType);
                 }
-                CameraExtensionCharacteristics.unregisterClient(mContext, mToken);
+                CameraExtensionCharacteristics.unregisterClient(mContext, mToken, mExtensionType);
             }
             mInitialized = false;
             mToken = null;
@@ -1000,7 +1004,8 @@
                 mStatsAggregator.commit(/*isFinal*/false);
                 try {
                     finishPipelineInitialization();
-                    CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
+                    CameraExtensionCharacteristics.initializeSession(
+                            mInitializeHandler, mExtensionType);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to initialize session! Extension service does"
                             + " not respond!");
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 6ed87fff..01dccd1 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -15,11 +15,14 @@
  */
 package android.hardware.location;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
+import android.chre.flags.Flags;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -185,23 +188,75 @@
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @ContextHubTransaction.Result
     public int sendMessageToNanoApp(@NonNull NanoAppMessage message) {
+        return doSendMessageToNanoApp(message, null);
+    }
+
+    /**
+     * Sends a reliable message to a nanoapp.
+     *
+     * This method is similar to {@link ContextHubClient#sendMessageToNanoApp} with the
+     * difference that it expects the message to be acknowledged by CHRE.
+     *
+     * The transaction succeeds after we received an ACK from CHRE without error.
+     * In all other cases the transaction will fail.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @NonNull
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public ContextHubTransaction<Void> sendReliableMessageToNanoApp(
+            @NonNull NanoAppMessage message) {
+        if (!Flags.reliableMessageImplementation()) {
+            return null;
+        }
+
+        ContextHubTransaction<Void> transaction =
+                new ContextHubTransaction<>(ContextHubTransaction.TYPE_RELIABLE_MESSAGE);
+
+        if (!mAttachedHub.supportsReliableMessages()) {
+            transaction.setResponse(new ContextHubTransaction.Response<Void>(
+                    ContextHubTransaction.RESULT_FAILED_NOT_SUPPORTED, null));
+            return transaction;
+        }
+
+        IContextHubTransactionCallback callback =
+                ContextHubTransactionHelper.createTransactionCallback(transaction);
+
+        @ContextHubTransaction.Result int result = doSendMessageToNanoApp(message, callback);
+        if (result != ContextHubTransaction.RESULT_SUCCESS) {
+            transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
+        }
+
+        return transaction;
+    }
+
+    /**
+     * Sends a message to a nanoapp.
+     *
+     * @param message The message to send.
+     * @param transactionCallback The callback to use when the message is reliable. null for regular
+     *         messages.
+     * @return A {@link ContextHubTransaction.Result} error code.
+     */
+    @ContextHubTransaction.Result
+    private int doSendMessageToNanoApp(@NonNull NanoAppMessage message,
+            @Nullable IContextHubTransactionCallback transactionCallback) {
         Objects.requireNonNull(message, "NanoAppMessage cannot be null");
 
         int maxPayloadBytes = mAttachedHub.getMaxPacketLengthBytes();
+
         byte[] payload = message.getMessageBody();
         if (payload != null && payload.length > maxPayloadBytes) {
-            Log.e(
-                    TAG,
-                    "Message ("
-                            + payload.length
-                            + " bytes) exceeds max payload length ("
-                            + maxPayloadBytes
-                            + " bytes)");
+            Log.e(TAG,
+                    "Message (%d bytes) exceeds max payload length (%d bytes)".formatted(
+                            payload.length, maxPayloadBytes));
             return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
         }
 
         try {
-            return mClientProxy.sendMessageToNanoApp(message);
+            if (transactionCallback == null) {
+                return mClientProxy.sendMessageToNanoApp(message);
+            }
+            return mClientProxy.sendReliableMessageToNanoApp(message, transactionCallback);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -224,16 +279,32 @@
     /** @hide */
     public synchronized void callbackFinished() {
         try {
-            while (mClientProxy == null) {
-                try {
-                    this.wait();
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                }
-            }
+            waitForClientProxy();
             mClientProxy.callbackFinished();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /** @hide */
+    public synchronized void reliableMessageCallbackFinished(int messageSequenceNumber,
+            byte errorCode) {
+        try {
+            waitForClientProxy();
+            mClientProxy.reliableMessageCallbackFinished(messageSequenceNumber, errorCode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    private void waitForClientProxy() {
+        while (mClientProxy == null) {
+            try {
+                this.wait();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
 }
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 51045a4..5012a79 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,9 +15,11 @@
  */
 package android.hardware.location;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.chre.flags.Flags;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -41,6 +43,7 @@
     private float mSleepPowerDrawMw;
     private float mPeakPowerDrawMw;
     private int mMaxPacketLengthBytes;
+    private boolean mSupportsReliableMessages;
     private byte mChreApiMajorVersion;
     private byte mChreApiMinorVersion;
     private short mChrePatchVersion;
@@ -71,6 +74,7 @@
         mSleepPowerDrawMw = contextHub.sleepPowerDrawMw;
         mPeakPowerDrawMw = contextHub.peakPowerDrawMw;
         mMaxPacketLengthBytes = contextHub.maxSupportedMsgLen;
+        mSupportsReliableMessages = false;
         mChrePlatformId = contextHub.chrePlatformId;
         mChreApiMajorVersion = contextHub.chreApiMajorVersion;
         mChreApiMinorVersion = contextHub.chreApiMinorVersion;
@@ -94,6 +98,8 @@
         mSleepPowerDrawMw = 0;
         mPeakPowerDrawMw = 0;
         mMaxPacketLengthBytes = contextHub.maxSupportedMessageLengthBytes;
+        mSupportsReliableMessages = Flags.reliableMessageImplementation()
+                && contextHub.supportsReliableMessages;
         mChrePlatformId = contextHub.chrePlatformId;
         mChreApiMajorVersion = contextHub.chreApiMajorVersion;
         mChreApiMinorVersion = contextHub.chreApiMinorVersion;
@@ -104,16 +110,25 @@
     }
 
     /**
-     * returns the maximum number of bytes that can be sent per message to the hub
+     * Returns the maximum number of bytes for a message to the hub.
      *
-     * @return int - maximum bytes that can be transmitted in a
-     *         single packet
+     * @return int - maximum bytes that can be transmitted in a single packet.
      */
     public int getMaxPacketLengthBytes() {
         return mMaxPacketLengthBytes;
     }
 
     /**
+     * Returns whether reliable messages are supported
+     *
+     * @return whether reliable messages are supported.
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public boolean supportsReliableMessages() {
+        return mSupportsReliableMessages;
+    }
+
+    /**
      * get the context hub unique identifer
      *
      * @return int - unique system wide identifier
@@ -164,7 +179,10 @@
      * @return int - platform version number
      */
     public int getStaticSwVersion() {
-        return (mChreApiMajorVersion << 24) | (mChreApiMinorVersion << 16) | (mChrePatchVersion);
+        // Version parts are all unsigned values.
+        return (Byte.toUnsignedInt(mChreApiMajorVersion) << 24)
+                | (Byte.toUnsignedInt(mChreApiMinorVersion) << 16)
+                | (Short.toUnsignedInt(mChrePatchVersion));
     }
 
     /**
@@ -284,12 +302,14 @@
         retVal += ", Toolchain version: 0x" + Integer.toHexString(mToolchainVersion);
         retVal += "\n\tPlatformVersion : 0x" + Integer.toHexString(mPlatformVersion);
         retVal += ", SwVersion : "
-                + mChreApiMajorVersion + "." + mChreApiMinorVersion + "." + mChrePatchVersion;
+                + Byte.toUnsignedInt(mChreApiMajorVersion) + "." + Byte.toUnsignedInt(
+                mChreApiMinorVersion) + "." + Short.toUnsignedInt(mChrePatchVersion);
         retVal += ", CHRE platform ID: 0x" + Long.toHexString(mChrePlatformId);
         retVal += "\n\tPeakMips : " + mPeakMips;
         retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
         retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
         retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
+        retVal += ", SupportsReliableMessage : " + mSupportsReliableMessages;
 
         return retVal;
     }
@@ -316,6 +336,8 @@
         proto.write(ContextHubInfoProto.SLEEP_POWER_DRAW_MW, mSleepPowerDrawMw);
         proto.write(ContextHubInfoProto.PEAK_POWER_DRAW_MW, mPeakPowerDrawMw);
         proto.write(ContextHubInfoProto.MAX_PACKET_LENGTH_BYTES, mMaxPacketLengthBytes);
+        proto.write(ContextHubInfoProto.SUPPORTS_RELIABLE_MESSAGES,
+                mSupportsReliableMessages);
     }
 
     @Override
@@ -339,6 +361,8 @@
                     && (other.getSleepPowerDrawMw() == mSleepPowerDrawMw)
                     && (other.getPeakPowerDrawMw() == mPeakPowerDrawMw)
                     && (other.getMaxPacketLengthBytes() == mMaxPacketLengthBytes)
+                    && (!Flags.reliableMessage()
+                            || (other.supportsReliableMessages() == mSupportsReliableMessages))
                     && Arrays.equals(other.getSupportedSensors(), mSupportedSensors)
                     && Arrays.equals(other.getMemoryRegions(), mMemoryRegions);
         }
@@ -367,6 +391,7 @@
         mSupportedSensors = new int[numSupportedSensors];
         in.readIntArray(mSupportedSensors);
         mMemoryRegions = in.createTypedArray(MemoryRegion.CREATOR);
+        mSupportsReliableMessages = in.readBoolean();
     }
 
     public int describeContents() {
@@ -393,6 +418,7 @@
         out.writeInt(mSupportedSensors.length);
         out.writeIntArray(mSupportedSensors);
         out.writeTypedArray(mMemoryRegions, flags);
+        out.writeBoolean(mSupportsReliableMessages);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ContextHubInfo> CREATOR
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 481ec72..3a58993 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -29,15 +29,15 @@
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
+import android.chre.flags.Flags;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.hardware.contexthub.ErrorCode;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -456,32 +456,6 @@
         }
     }
 
-    /**
-     * Helper function to generate a stub for a non-query transaction callback.
-     *
-     * @param transaction the transaction to unblock when complete
-     *
-     * @return the callback
-     *
-     * @hide
-     */
-    private IContextHubTransactionCallback createTransactionCallback(
-            ContextHubTransaction<Void> transaction) {
-        return new IContextHubTransactionCallback.Stub() {
-            @Override
-            public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
-                Log.e(TAG, "Received a query callback on a non-query request");
-                transaction.setResponse(new ContextHubTransaction.Response<Void>(
-                        ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
-            }
-
-            @Override
-            public void onTransactionComplete(int result) {
-                transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
-            }
-        };
-    }
-
    /**
     * Helper function to generate a stub for a query transaction callback.
     *
@@ -532,7 +506,8 @@
 
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_LOAD_NANOAPP);
-        IContextHubTransactionCallback callback = createTransactionCallback(transaction);
+        IContextHubTransactionCallback callback =
+                ContextHubTransactionHelper.createTransactionCallback(transaction);
 
         try {
             mService.loadNanoAppOnHub(hubInfo.getId(), callback, appBinary);
@@ -560,7 +535,8 @@
 
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_UNLOAD_NANOAPP);
-        IContextHubTransactionCallback callback = createTransactionCallback(transaction);
+        IContextHubTransactionCallback callback =
+                ContextHubTransactionHelper.createTransactionCallback(transaction);
 
         try {
             mService.unloadNanoAppFromHub(hubInfo.getId(), callback, nanoAppId);
@@ -588,7 +564,8 @@
 
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_ENABLE_NANOAPP);
-        IContextHubTransactionCallback callback = createTransactionCallback(transaction);
+        IContextHubTransactionCallback callback =
+                ContextHubTransactionHelper.createTransactionCallback(transaction);
 
         try {
             mService.enableNanoApp(hubInfo.getId(), callback, nanoAppId);
@@ -616,7 +593,8 @@
 
         ContextHubTransaction<Void> transaction =
                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_DISABLE_NANOAPP);
-        IContextHubTransactionCallback callback = createTransactionCallback(transaction);
+        IContextHubTransactionCallback callback =
+                ContextHubTransactionHelper.createTransactionCallback(transaction);
 
         try {
             mService.disableNanoApp(hubInfo.getId(), callback, nanoAppId);
@@ -732,7 +710,14 @@
                 executor.execute(
                         () -> {
                             callback.onMessageFromNanoApp(client, message);
-                            client.callbackFinished();
+                            if (Flags.reliableMessage()
+                                        && Flags.reliableMessageImplementation()
+                                        && message.isReliable()) {
+                                client.reliableMessageCallbackFinished(
+                                        message.getMessageSequenceNumber(), ErrorCode.OK);
+                            } else {
+                                client.callbackFinished();
+                            }
                         });
             }
 
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index d11e0a9..4060f4c 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -16,9 +16,11 @@
 package android.hardware.location;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.chre.flags.Flags;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 
@@ -57,7 +59,8 @@
             TYPE_UNLOAD_NANOAPP,
             TYPE_ENABLE_NANOAPP,
             TYPE_DISABLE_NANOAPP,
-            TYPE_QUERY_NANOAPPS
+            TYPE_QUERY_NANOAPPS,
+            TYPE_RELIABLE_MESSAGE,
     })
     public @interface Type { }
 
@@ -66,6 +69,8 @@
     public static final int TYPE_ENABLE_NANOAPP = 2;
     public static final int TYPE_DISABLE_NANOAPP = 3;
     public static final int TYPE_QUERY_NANOAPPS = 4;
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public static final int TYPE_RELIABLE_MESSAGE = 5;
 
     /**
      * Constants describing the result of a transaction or request through the Context Hub Service.
@@ -81,7 +86,8 @@
             RESULT_FAILED_AT_HUB,
             RESULT_FAILED_TIMEOUT,
             RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
-            RESULT_FAILED_HAL_UNAVAILABLE
+            RESULT_FAILED_HAL_UNAVAILABLE,
+            RESULT_FAILED_NOT_SUPPORTED,
     })
     public @interface Result {}
     public static final int RESULT_SUCCESS = 0;
@@ -117,6 +123,11 @@
      * Failure mode when the Context Hub HAL was not available.
      */
     public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8;
+    /**
+     * Failure mode when the operation is not supported.
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public static final int RESULT_FAILED_NOT_SUPPORTED = 9;
 
     /**
      * A class describing the response for a ContextHubTransaction.
@@ -221,6 +232,11 @@
                 return upperCase ? "Disable" : "disable";
             case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
                 return upperCase ? "Query" : "query";
+            case ContextHubTransaction.TYPE_RELIABLE_MESSAGE: {
+                if (Flags.reliableMessage()) {
+                    return upperCase ? "Reliable Message" : "reliable message";
+                }
+            }
             default:
                 return upperCase ? "Unknown" : "unknown";
         }
diff --git a/core/java/android/hardware/location/ContextHubTransactionHelper.java b/core/java/android/hardware/location/ContextHubTransactionHelper.java
new file mode 100644
index 0000000..66c03f4
--- /dev/null
+++ b/core/java/android/hardware/location/ContextHubTransactionHelper.java
@@ -0,0 +1,83 @@
+/*
+ * 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.location;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Helper class to generate {@link IContextHubTransactionCallback}.
+ *
+ * @hide
+ */
+public class ContextHubTransactionHelper {
+    private static final String TAG = "ContextHubTransactionHelper";
+
+    /**
+     * Helper to generate a stub for a query nanoapp transaction callback.
+     *
+     * @param transaction the transaction to unblock when complete
+     * @return the callback
+     * @hide
+     */
+    public static IContextHubTransactionCallback createNanoAppQueryCallback(
+            @NonNull() ContextHubTransaction<List<NanoAppState>> transaction) {
+        Objects.requireNonNull(transaction, "transaction cannot be null");
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+                transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+                        result, nanoappList));
+            }
+
+            @Override
+            public void onTransactionComplete(int result) {
+                Log.e(TAG, "Received a non-query callback on a query request");
+                transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+                        ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
+            }
+        };
+    }
+
+    /**
+     * Helper to generate a stub for a non-query transaction callback.
+     *
+     * @param transaction the transaction to unblock when complete
+     * @return the callback
+     * @hide
+     */
+    public static IContextHubTransactionCallback createTransactionCallback(
+            @NonNull() ContextHubTransaction<Void> transaction) {
+        Objects.requireNonNull(transaction, "transaction cannot be null");
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+                Log.e(TAG, "Received a query callback on a non-query request");
+                transaction.setResponse(new ContextHubTransaction.Response<Void>(
+                        ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
+            }
+
+            @Override
+            public void onTransactionComplete(int result) {
+                transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
+            }
+        };
+    }
+
+}
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
index 1ee342e9..ca23705 100644
--- a/core/java/android/hardware/location/IContextHubClient.aidl
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -18,21 +18,35 @@
 
 import android.app.PendingIntent;
 import android.hardware.location.NanoAppMessage;
+import android.hardware.location.IContextHubTransactionCallback;
 
 /**
  * @hide
  */
 interface IContextHubClient {
-
-    // Sends a message to a nanoapp
+    // Sends a message to a nanoapp.
     int sendMessageToNanoApp(in NanoAppMessage message);
 
-    // Closes the connection with the Context Hub
+    // Closes the connection with the Context Hub.
     void close();
 
     // Returns the unique ID for this client.
     int getId();
 
-    // Notify direct-call message callback completed
+    // Notify the framework that a client callback has finished executing.
     void callbackFinished();
+
+    // Notify the framework that a reliable message client callback has
+    // finished executing.
+    void reliableMessageCallbackFinished(int messageSequenceNumber, byte errorCode);
+
+    /**
+     * Sends a reliable message to a nanoapp.
+     *
+     * @param message The message to send.
+     * @param transactionCallback The transaction callback for reliable message.
+     */
+    int sendReliableMessageToNanoApp(
+        in NanoAppMessage message,
+        in IContextHubTransactionCallback transactionCallback);
 }
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 7ac1dd1..48aa1bd 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -15,9 +15,11 @@
  */
 package android.hardware.location;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.chre.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -39,13 +41,17 @@
     private int mMessageType;
     private byte[] mMessageBody;
     private boolean mIsBroadcasted;
+    private boolean mIsReliable;
+    private int mMessageSequenceNumber;
 
-    private NanoAppMessage(
-            long nanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
+    private NanoAppMessage(long nanoAppId, int messageType, byte[] messageBody,
+            boolean broadcasted, boolean isReliable, int messageSequenceNumber) {
         mNanoAppId = nanoAppId;
         mMessageType = messageType;
         mMessageBody = messageBody;
         mIsBroadcasted = broadcasted;
+        mIsReliable = isReliable;
+        mMessageSequenceNumber = messageSequenceNumber;
     }
 
     /**
@@ -62,10 +68,10 @@
      *
      * @return the NanoAppMessage object
      */
-    public static NanoAppMessage createMessageToNanoApp(
-            long targetNanoAppId, int messageType, byte[] messageBody) {
-        return new NanoAppMessage(
-                targetNanoAppId, messageType, messageBody, false /* broadcasted */);
+    public static NanoAppMessage createMessageToNanoApp(long targetNanoAppId, int messageType,
+            byte[] messageBody) {
+        return new NanoAppMessage(targetNanoAppId, messageType, messageBody,
+                false /* broadcasted */, false /* isReliable */, 0 /* messageSequenceNumber */);
     }
 
     /**
@@ -81,9 +87,33 @@
      *
      * @return the NanoAppMessage object
      */
-    public static NanoAppMessage createMessageFromNanoApp(
-            long sourceNanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
-        return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted);
+    public static NanoAppMessage createMessageFromNanoApp(long sourceNanoAppId, int messageType,
+            byte[] messageBody, boolean broadcasted) {
+        return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted,
+                false /* isReliable */, 0 /* messageSequenceNumber */);
+    }
+
+    /**
+     * Creates a NanoAppMessage object sent from a nanoapp.
+     *
+     * This factory method is intended only to be used by the Context Hub Service when delivering
+     * messages from a nanoapp to clients.
+     *
+     * @param sourceNanoAppId the ID of the nanoapp that the message was sent from
+     * @param messageType the nanoapp-dependent message type
+     * @param messageBody the byte array message contents
+     * @param broadcasted {@code true} if the message was broadcasted, {@code false} otherwise
+     * @param isReliable if the NanoAppMessage is reliable
+     * @param messageSequenceNumber the message sequence number of the NanoAppMessage
+     *
+     * @return the NanoAppMessage object
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public static @NonNull NanoAppMessage createMessageFromNanoApp(long sourceNanoAppId,
+            int messageType, @NonNull byte[] messageBody, boolean broadcasted, boolean isReliable,
+            int messageSequenceNumber) {
+        return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted,
+                isReliable, messageSequenceNumber);
     }
 
     /**
@@ -114,6 +144,40 @@
         return mIsBroadcasted;
     }
 
+    /**
+     * Returns if the message is reliable. The default value is {@code false}
+     * @return {@code true} if the message is reliable, {@code false} otherwise
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public boolean isReliable() {
+        return mIsReliable;
+    }
+
+    /**
+     * Returns the message sequence number. The default value is 0
+     * @return the message sequence number of the message
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public int getMessageSequenceNumber() {
+        return mMessageSequenceNumber;
+    }
+
+    /**
+     * Sets the isReliable field of the message
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public void setIsReliable(boolean isReliable) {
+        mIsReliable = isReliable;
+    }
+
+    /**
+     * Sets the message sequence number of the message
+     */
+    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
+    public void setMessageSequenceNumber(int messageSequenceNumber) {
+        mMessageSequenceNumber = messageSequenceNumber;
+    }
+
     private NanoAppMessage(Parcel in) {
         mNanoAppId = in.readLong();
         mIsBroadcasted = (in.readInt() == 1);
@@ -122,6 +186,9 @@
         int msgSize = in.readInt();
         mMessageBody = new byte[msgSize];
         in.readByteArray(mMessageBody);
+
+        mIsReliable = (in.readInt() == 1);
+        mMessageSequenceNumber = in.readInt();
     }
 
     @Override
@@ -137,6 +204,9 @@
 
         out.writeInt(mMessageBody.length);
         out.writeByteArray(mMessageBody);
+
+        out.writeInt(mIsReliable ? 1 : 0);
+        out.writeInt(mMessageSequenceNumber);
     }
 
     public static final @NonNull Creator<NanoAppMessage> CREATOR =
@@ -159,7 +229,9 @@
 
         String ret = "NanoAppMessage[type = " + mMessageType + ", length = " + mMessageBody.length
                 + " bytes, " + (mIsBroadcasted ? "broadcast" : "unicast") + ", nanoapp = 0x"
-                + Long.toHexString(mNanoAppId) + "](";
+                + Long.toHexString(mNanoAppId) + ", isReliable = "
+                + (mIsReliable ? "true" : "false") + ", messageSequenceNumber = "
+                + mMessageSequenceNumber + "](";
         if (length > 0) {
             ret += "data = 0x";
         }
@@ -190,7 +262,11 @@
             isEqual = (other.getNanoAppId() == mNanoAppId)
                     && (other.getMessageType() == mMessageType)
                     && (other.isBroadcastMessage() == mIsBroadcasted)
-                    && Arrays.equals(other.getMessageBody(), mMessageBody);
+                    && Arrays.equals(other.getMessageBody(), mMessageBody)
+                    && (!Flags.reliableMessage()
+                            || (other.isReliable() == mIsReliable))
+                    && (!Flags.reliableMessage()
+                            || (other.getMessageSequenceNumber() == mMessageSequenceNumber));
         }
 
         return isEqual;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b68b94d..a6b2ed0 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.location.GnssSignalQuality;
+import android.net.NetworkCapabilities;
 import android.os.BatteryStatsManager.WifiState;
 import android.os.BatteryStatsManager.WifiSupplState;
 import android.server.ServerProtoEnums;
@@ -59,6 +60,7 @@
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerStats;
+import com.android.net.module.util.NetworkCapabilitiesUtils;
 
 import com.google.android.collect.Lists;
 
@@ -2734,26 +2736,28 @@
         "emngcy", "other"
     };
 
+    public static final int NUM_ALL_NETWORK_TYPES = getAllNetworkTypesCount();
     public static final int DATA_CONNECTION_OUT_OF_SERVICE = 0;
-    public static final int DATA_CONNECTION_EMERGENCY_SERVICE = getEmergencyNetworkConnectionType();
-    public static final int DATA_CONNECTION_OTHER = DATA_CONNECTION_EMERGENCY_SERVICE + 1;
+    public static final int DATA_CONNECTION_EMERGENCY_SERVICE = NUM_ALL_NETWORK_TYPES + 1;
+    public static final int DATA_CONNECTION_OTHER = NUM_ALL_NETWORK_TYPES + 2;
 
     @UnsupportedAppUsage
-    public static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER + 1;
+    public static final int NUM_DATA_CONNECTION_TYPES = NUM_ALL_NETWORK_TYPES + 3;
+
 
     @android.ravenwood.annotation.RavenwoodReplace
-    private static int getEmergencyNetworkConnectionType() {
+    public static int getAllNetworkTypesCount() {
         int count = TelephonyManager.getAllNetworkTypes().length;
         if (DATA_CONNECTION_NAMES.length != count + 3) {        // oos, emngcy, other
             throw new IllegalStateException(
                     "DATA_CONNECTION_NAMES length does not match network type count. "
                     + "Expected: " + (count + 3) + ", actual:" + DATA_CONNECTION_NAMES.length);
         }
-        return count + 1;
+        return count;
     }
 
-    private static int getEmergencyNetworkConnectionType$ravenwood() {
-        return DATA_CONNECTION_NAMES.length - 2;
+    public static int getAllNetworkTypesCount$ravenwood() {
+        return DATA_CONNECTION_NAMES.length - 3;  // oos, emngcy, other
     }
 
     /**
@@ -9071,4 +9075,31 @@
     protected static boolean isKernelStatsAvailable$ravenwood() {
         return false;
     }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    protected static int getDisplayTransport(int[] transports) {
+        return NetworkCapabilitiesUtils.getDisplayTransport(transports);
+    }
+
+    // See NetworkCapabilitiesUtils
+    private static final int[] DISPLAY_TRANSPORT_PRIORITIES = new int[] {
+            NetworkCapabilities.TRANSPORT_VPN,
+            NetworkCapabilities.TRANSPORT_CELLULAR,
+            NetworkCapabilities.TRANSPORT_WIFI_AWARE,
+            NetworkCapabilities.TRANSPORT_BLUETOOTH,
+            NetworkCapabilities.TRANSPORT_WIFI,
+            NetworkCapabilities.TRANSPORT_ETHERNET,
+            NetworkCapabilities.TRANSPORT_USB
+    };
+
+    protected static int getDisplayTransport$ravenwood(int[] transports) {
+        for (int transport : DISPLAY_TRANSPORT_PRIORITIES) {
+            for (int t : transports) {
+                if (t == transport) {
+                    return transport;
+                }
+            }
+        }
+        return transports[0];
+    }
 }
diff --git a/core/java/android/os/BluetoothBatteryStats.java b/core/java/android/os/BluetoothBatteryStats.java
index 3d99a08..fa8f39d 100644
--- a/core/java/android/os/BluetoothBatteryStats.java
+++ b/core/java/android/os/BluetoothBatteryStats.java
@@ -26,6 +26,7 @@
  *
  * @hide
  */
[email protected]
 public class BluetoothBatteryStats implements Parcelable {
 
     /** @hide */
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index eb5b511..4b170f3 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -99,3 +99,6 @@
 # SystemConfig
 per-file ISystemConfig.aidl = file:/PACKAGE_MANAGER_OWNERS
 per-file SystemConfigManager.java = file:/PACKAGE_MANAGER_OWNERS
+
+# ProfilingService
+per-file ProfilingServiceManager.java = file:/PERFORMANCE_OWNERS
diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java
index a2ff078..23ba0c6 100644
--- a/core/java/android/os/UserBatteryConsumer.java
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -34,6 +34,7 @@
  *
  * {@hide}
  */
[email protected]
 public class UserBatteryConsumer extends BatteryConsumer {
     static final int CONSUMER_TYPE_USER = 2;
 
diff --git a/core/java/android/os/WakeLockStats.java b/core/java/android/os/WakeLockStats.java
index 05a7313..69e70a0 100644
--- a/core/java/android/os/WakeLockStats.java
+++ b/core/java/android/os/WakeLockStats.java
@@ -25,6 +25,7 @@
  * Snapshot of wake lock stats.
  *  @hide
  */
[email protected]
 public final class WakeLockStats implements Parcelable {
 
     /** @hide */
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 380962c..4ae0a57 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -23,6 +23,7 @@
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.os.UserHandle;
 import android.permission.IOnPermissionsChangeListener;
+import android.permission.PermissionManager.PermissionState;
 
 /**
  * Interface to communicate directly with the permission manager service.
@@ -103,4 +104,12 @@
             int userId);
 
     int checkUidPermission(int uid, String permissionName, int deviceId);
+
+    Map<String, PermissionState> getAllPermissionStates(String packageName, String persistentDeviceId, int userId);
 }
+
+/**
+ * Data class for the state of a permission requested by a package
+ * @hide
+ */
+parcelable PermissionManager.PermissionState;
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index d6e8ce7..e6b8102 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -66,6 +66,8 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -89,6 +91,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -1785,6 +1788,29 @@
     }
 
     /**
+     * Gets the permission states for requested package and persistent device.
+     *
+     * @param packageName name of the package you are checking against
+     * @param persistentDeviceId id of the persistent device you are checking against
+     * @return mapping of all permission states keyed by their permission names
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS)
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+    public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
+            @NonNull String persistentDeviceId) {
+        try {
+            return mPermissionManager.getAllPermissionStates(packageName, persistentDeviceId,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Make checkPermission() above bypass the permission cache in this process.
      *
      * @hide
@@ -1979,4 +2005,68 @@
             }
         }
     }
+
+    /**
+     * Data class for the state of a permission requested by a package
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+    public static final class PermissionState implements Parcelable {
+        private final boolean mGranted;
+        private final int mFlags;
+
+        /** @hide */
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+        public PermissionState(boolean granted, int flags) {
+            mGranted = granted;
+            mFlags = flags;
+        }
+
+        /**
+         * Returns whether this permission is granted
+         */
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+        public boolean isGranted() {
+            return mGranted;
+        }
+
+        /**
+         * Returns the flags associated with this permission state
+         * @see PackageManager#getPermissionFlags
+         */
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+        public int getFlags() {
+            return mFlags;
+        }
+
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+        @Override
+        public void writeToParcel(@NonNull Parcel parcel, int flags) {
+            parcel.writeBoolean(mGranted);
+            parcel.writeInt(mFlags);
+        }
+
+        private PermissionState(Parcel parcel) {
+            this(parcel.readBoolean(), parcel.readInt());
+        }
+
+        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+        public static final @NonNull Creator<PermissionState> CREATOR = new Creator<>() {
+            public PermissionState createFromParcel(Parcel source) {
+                return new PermissionState(source);
+            }
+
+            public PermissionState[] newArray(int size) {
+                return new PermissionState[size];
+            }
+        };
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index be9915c..b026ce9 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -668,6 +668,23 @@
             "android.settings.MANAGE_APP_LONG_RUNNING_JOBS";
 
     /**
+     * Activity Action: Show settings to allow configuration of
+     * {@link Manifest.permission#RUN_BACKUP_JOBS} permission.
+     *
+     * Input: Optionally, the Intent's data URI can specify the application package name to
+     * directly invoke the management GUI specific to the package name. For example
+     * "package:com.my.app".
+     * <p>
+     * Output: When a package data uri is passed as input, the activity result is set to
+     * {@link android.app.Activity#RESULT_OK} if the permission was granted to the app. Otherwise,
+     * the result is set to {@link android.app.Activity#RESULT_CANCELED}.
+     */
+    @FlaggedApi(Flags.FLAG_BACKUP_TASKS_SETTINGS_SCREEN)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_REQUEST_RUN_BACKUP_JOBS =
+            "android.settings.REQUEST_RUN_BACKUP_JOBS";
+
+    /**
      * Activity Action: Show settings to allow configuration of cross-profile access for apps
      *
      * Input: Optionally, the Intent's data URI can specify the application package name to
@@ -11830,6 +11847,13 @@
         public static final String MEDIA_CONTROLS_LOCK_SCREEN = "media_controls_lock_screen";
 
         /**
+         * Whether to enable camera extensions software fallback.
+         * @hide
+         */
+        @Readable
+        public static final String CAMERA_EXTENSIONS_FALLBACK = "camera_extensions_fallback";
+
+        /**
          * Controls whether contextual suggestions can be shown in the media controls.
          * @hide
          */
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index 0f12b13..ea1ac27 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -13,3 +13,10 @@
     description: "This flag controls new E2EE contact keys API"
     bug: "290696572"
 }
+
+flag {
+    name: "backup_tasks_settings_screen"
+    namespace: "backstage_power"
+    description: "Add a new settings page for the RUN_BACKUP_JOBS permission."
+    bug: "320563660"
+}
diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java
index a8fcf86..df07256 100644
--- a/core/java/android/service/autofill/InlineSuggestionRenderService.java
+++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java
@@ -39,6 +39,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
+import android.window.InputTransferToken;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -166,7 +167,7 @@
                     PixelFormat.TRANSPARENT);
 
             final SurfaceControlViewHost host = new SurfaceControlViewHost(this, getDisplay(),
-                    hostInputToken, "InlineSuggestionRenderService");
+                    new InputTransferToken(hostInputToken), "InlineSuggestionRenderService");
             host.setView(suggestionRoot, lp);
 
             // Set the suggestion view to be non-focusable so that if its background is set to a
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index 8aeaacf..3cc7f5a 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -1,6 +1,13 @@
 package: "android.service.chooser"
 
 flag {
+  name: "chooser_album_text"
+  namespace: "intentresolver"
+  description: "Flag controlling the album text subtype hint for sharesheet"
+  bug: "323380224"
+}
+
+flag {
   name: "support_nfc_resolver"
   namespace: "systemui"
   description: "This flag controls the new NFC 'resolver' activity"
diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java
index f844423..9b1d231 100644
--- a/core/java/android/service/games/GameSessionService.java
+++ b/core/java/android/service/games/GameSessionService.java
@@ -24,12 +24,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.view.Display;
 import android.view.SurfaceControlViewHost;
 import android.view.WindowManager;
+import android.window.InputTransferToken;
 
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -118,14 +118,13 @@
             return;
         }
 
-        IBinder hostToken = new Binder();
-
         // Use a WindowContext so that views attached to the SurfaceControlViewHost will receive
         // configuration changes (rather than always perceiving the global configuration).
         final Context windowContext = createWindowContext(display,
                 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, /*options=*/ null);
         SurfaceControlViewHost surfaceControlViewHost =
-                new SurfaceControlViewHost(windowContext, display, hostToken, "GameSessionService");
+                new SurfaceControlViewHost(windowContext, display,
+                        new InputTransferToken(), "GameSessionService");
 
         gameSession.attach(
                 gameSessionController,
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index 0556188..f67dcff 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -37,4 +37,5 @@
             in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback);
     void stopDetection(in String packageName);
     void queryServiceStatus(in int[] eventTypes, in String packageName, in RemoteCallback callback);
+    void killProcess();
 }
\ No newline at end of file
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
index d25cff7..bb6e030 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -32,6 +32,7 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
+import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.SharedMemory;
 import android.service.ambientcontext.AmbientContextDetectionResult;
@@ -242,6 +243,13 @@
                     WearableSensingService.this.onQueryServiceStatus(
                             new HashSet<>(Arrays.asList(events)), packageName, consumer);
                 }
+
+                /** {@inheritDoc} */
+                @Override
+                public void killProcess() {
+                    Slog.d(TAG, "#killProcess");
+                    Process.killProcess(Process.myPid());
+                }
             };
         }
         Slog.w(TAG, "Incorrect service interface, returning null.");
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 52f4855..ffe0c7169 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -23,6 +23,7 @@
 import android.graphics.Region;
 import android.hardware.HardwareBuffer;
 import android.os.IBinder;
+import android.window.InputTransferToken;
 import android.window.SurfaceSyncGroup;
 
 import com.android.window.flags.Flags;
@@ -195,6 +196,20 @@
     }
 
     /**
+     * Gets the token used for associating this {@link AttachedSurfaceControl} with an embedded
+     * {@link SurfaceControlViewHost} or {@link SurfaceControl}
+     *
+     * @return The SurfaceControlViewHost link token.  This can return {@code null} if the
+     * {@link AttachedSurfaceControl} was created with no registered input
+     * @hide
+     */
+    @Nullable
+    default InputTransferToken getInputTransferToken() {
+        throw new UnsupportedOperationException("The getHostToken needs to be "
+                + "implemented before making this call.");
+    }
+
+    /**
      * Transfer the currently in progress touch gesture from the host to the requested
      * {@link SurfaceControlViewHost.SurfacePackage}. This requires that the
      * SurfaceControlViewHost was created with the current host's inputToken.
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index fbadef3..0006139 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -86,6 +86,7 @@
  * that are currently attached and whether mirroring has been enabled.
  * </p>
  */
[email protected]
 public final class Display {
     private static final String TAG = "Display";
     private static final boolean DEBUG = false;
@@ -1998,6 +1999,7 @@
      * display power state. In SUSPEND states, updates are absolutely forbidden.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isSuspendedState(int state) {
         return state == STATE_OFF || state == STATE_DOZE_SUSPEND || state == STATE_ON_SUSPEND;
     }
@@ -2007,6 +2009,7 @@
      * specified display power state.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isDozeState(int state) {
         return state == STATE_DOZE || state == STATE_DOZE_SUSPEND;
     }
@@ -2016,6 +2019,7 @@
      * or {@link #STATE_VR}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isActiveState(int state) {
         return state == STATE_ON || state == STATE_VR;
     }
@@ -2024,6 +2028,7 @@
      * Returns true if the display is in an off state such as {@link #STATE_OFF}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isOffState(int state) {
         return state == STATE_OFF;
     }
@@ -2033,6 +2038,7 @@
      * or {@link #STATE_VR} or {@link #STATE_ON_SUSPEND}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isOnState(int state) {
         return state == STATE_ON || state == STATE_VR || state == STATE_ON_SUSPEND;
     }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 99863d0..b5b81d1 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -73,6 +73,7 @@
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
+import android.window.InputTransferToken;
 import android.window.ScreenCapture;
 import android.window.TrustedPresentationThresholds;
 import android.window.WindowContextInfo;
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index d2c25cd..55e49f8 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -36,6 +36,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.window.ClientWindowFrames;
+import android.window.InputTransferToken;
 import android.window.OnBackInvokedCallbackInfo;
 
 import java.util.List;
@@ -310,8 +311,9 @@
     * be used as unique identifier.
     */
     void grantInputChannel(int displayId, in SurfaceControl surface, in IBinder clientToken,
-            in IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type,
-            in IBinder windowToken, in IBinder focusGrantToken, String inputHandleName,
+            in @nullable InputTransferToken hostInputTransferToken, int flags, int privateFlags,
+            int inputFeatures, int type, in IBinder windowToken,
+            in InputTransferToken embeddedInputTransferToken, String inputHandleName,
             out InputChannel outInputChannel);
 
     /**
@@ -332,7 +334,8 @@
      *                     should be transferred back to the host window. If there is no host
      *                     window, the system will try to find a new focus target.
      */
-    void grantEmbeddedWindowFocus(IWindow window, in IBinder inputToken, boolean grantFocus);
+    void grantEmbeddedWindowFocus(IWindow window, in InputTransferToken inputToken,
+            boolean grantFocus);
 
     /**
      * Generates an DisplayHash that can be used to validate whether specific content was on
@@ -369,7 +372,8 @@
 
     boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow);
 
-    boolean transferHostTouchGestureToEmbedded(IWindow hostWindow, IBinder transferTouchToken);
+    boolean transferHostTouchGestureToEmbedded(IWindow hostWindow,
+        in InputTransferToken transferTouchToken);
 
     /**
      * Moves the focus to the adjacent window if there is one in the given direction. This can only
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 5249fd5..58765b4 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -30,6 +30,7 @@
 import android.util.Log;
 import android.view.accessibility.IAccessibilityEmbeddedConnection;
 import android.window.ISurfaceSyncGroup;
+import android.window.InputTransferToken;
 import android.window.WindowTokenClient;
 
 import dalvik.system.CloseGuard;
@@ -150,15 +151,16 @@
     public static final class SurfacePackage implements Parcelable {
         private SurfaceControl mSurfaceControl;
         private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
-        private final IBinder mInputToken;
+        private final InputTransferToken mInputTransferToken;
         @NonNull
         private final ISurfaceControlViewHost mRemoteInterface;
 
         SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
-                IBinder inputToken, @NonNull ISurfaceControlViewHost ri) {
+                InputTransferToken inputTransferToken,
+                @NonNull ISurfaceControlViewHost ri) {
             mSurfaceControl = sc;
             mAccessibilityEmbeddedConnection = connection;
-            mInputToken = inputToken;
+            mInputTransferToken = inputTransferToken;
             mRemoteInterface = ri;
         }
 
@@ -178,7 +180,7 @@
                 mSurfaceControl = new SurfaceControl(otherSurfaceControl, "SurfacePackage");
             }
             mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection;
-            mInputToken = other.mInputToken;
+            mInputTransferToken = other.mInputTransferToken;
             mRemoteInterface = other.mRemoteInterface;
         }
 
@@ -188,9 +190,8 @@
             mSurfaceControl.setUnreleasedWarningCallSite("SurfacePackage(Parcel)");
             mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface(
                     in.readStrongBinder());
-            mInputToken = in.readStrongBinder();
-            mRemoteInterface = ISurfaceControlViewHost.Stub.asInterface(
-                in.readStrongBinder());
+            mInputTransferToken = InputTransferToken.CREATOR.createFromParcel(in);
+            mRemoteInterface = ISurfaceControlViewHost.Stub.asInterface(in.readStrongBinder());
         }
 
         /**
@@ -269,7 +270,7 @@
         public void writeToParcel(@NonNull Parcel out, int flags) {
             mSurfaceControl.writeToParcel(out, flags);
             out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder());
-            out.writeStrongBinder(mInputToken);
+            mInputTransferToken.writeToParcel(out, flags);
             out.writeStrongBinder(mRemoteInterface.asBinder());
         }
 
@@ -287,13 +288,22 @@
         }
 
         /**
-         * Returns an input token used which can be used to request focus on the embedded surface
-         * or to transfer touch gesture to the embedded surface.
+         * Gets an {@link InputTransferToken} which can be used to request focus on the embedded
+         * surface or to transfer touch gesture to the embedded surface.
+         * @return the InputTransferToken associated with {@link SurfacePackage}
+         * @see AttachedSurfaceControl#transferHostTouchGestureToEmbedded(SurfacePackage)
          *
          * @hide
          */
-        public IBinder getInputToken() {
-            return mInputToken;
+        @Nullable
+        public InputTransferToken getInputTransferToken() {
+            return mInputTransferToken;
+        }
+
+        @Override
+        public String toString() {
+            return "{inputTransferToken=" + getInputTransferToken() + " remoteInterface="
+                    + getRemoteInterface() + "}";
         }
 
         public static final @NonNull Creator<SurfacePackage> CREATOR
@@ -335,7 +345,8 @@
      */
     public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display,
             @Nullable IBinder hostToken) {
-        this(context, display, hostToken, "untracked");
+        this(context, display, hostToken == null ? null : new InputTransferToken(hostToken),
+                "untracked");
     }
 
     /**
@@ -353,7 +364,7 @@
      * @hide
      */
     public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display,
-            @Nullable IBinder hostToken, @NonNull String callsite) {
+            @Nullable InputTransferToken hostToken, @NonNull String callsite) {
         mSurfaceControl = new SurfaceControl.Builder()
                 .setContainerLayer()
                 .setName("SurfaceControlViewHost")
@@ -533,7 +544,7 @@
      *
      * @hide
      */
-    public IBinder getInputTransferToken() {
+    public InputTransferToken getInputTransferToken() {
         return mWm.getInputTransferToken(getWindowToken().asBinder());
     }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 108de28..9caf7a6 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -2099,7 +2099,7 @@
         }
         try {
             viewRoot.mWindowSession.grantEmbeddedWindowFocus(viewRoot.mWindow,
-                    mSurfacePackage.getInputToken(), gainFocus);
+                    mSurfacePackage.getInputTransferToken(), gainFocus);
         } catch (Exception e) {
             Log.e(TAG, System.identityHashCode(this)
                     + "Exception requesting focus on embedded window", e);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 32e6069..07c9795 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -91,10 +91,10 @@
 import static android.view.accessibility.Flags.fixMergedContentChangeEvent;
 import static android.view.accessibility.Flags.forceInvertColor;
 import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
+import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
+import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
-import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
-import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
 
 import static com.android.input.flags.Flags.enablePointerChoreographer;
 
@@ -218,6 +218,7 @@
 import android.window.BackEvent;
 import android.window.ClientWindowFrames;
 import android.window.CompatOnBackInvokedCallback;
+import android.window.InputTransferToken;
 import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
@@ -11225,6 +11226,18 @@
         return getInputToken();
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Nullable
+    @Override
+    public InputTransferToken getInputTransferToken() {
+        IBinder inputToken = getInputToken();
+        if (inputToken == null) {
+            return null;
+        }
+        return new InputTransferToken(inputToken);
+    }
     @NonNull
     public IBinder getWindowToken() {
         return mAttachInfo.mWindowToken;
@@ -12432,7 +12445,7 @@
         final IWindowSession realWm = WindowManagerGlobal.getWindowSession();
         try {
             return realWm.transferHostTouchGestureToEmbedded(mWindow,
-                    surfacePackage.getInputToken());
+                    surfacePackage.getInputTransferToken());
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
         }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index f92c31b..1428963 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -41,6 +41,7 @@
 import android.util.SparseArray;
 import android.view.inputmethod.InputMethodManager;
 import android.window.ITrustedPresentationListener;
+import android.window.InputTransferToken;
 import android.window.TrustedPresentationThresholds;
 
 import com.android.internal.annotations.GuardedBy;
@@ -839,7 +840,7 @@
     }
 
     void registerBatchedSurfaceControlInputReceiver(int displayId,
-            @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+            @NonNull InputTransferToken hostToken, @NonNull SurfaceControl surfaceControl,
             @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
         IBinder clientToken = new Binder();
         InputChannel inputChannel = new InputChannel();
@@ -866,8 +867,8 @@
         }
     }
 
-    void registerUnbatchedSurfaceControlInputReceiver(
-            int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+    void registerUnbatchedSurfaceControlInputReceiver(int displayId,
+            @NonNull InputTransferToken hostToken, @NonNull SurfaceControl surfaceControl,
             @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
         IBinder clientToken = new Binder();
         InputChannel inputChannel = new InputChannel();
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index eaf45c4..ae00b70 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -39,6 +39,7 @@
 import android.os.StrictMode;
 import android.util.Log;
 import android.window.ITaskFpsCallback;
+import android.window.InputTransferToken;
 import android.window.TaskFpsCallback;
 import android.window.TrustedPresentationThresholds;
 import android.window.WindowContext;
@@ -531,7 +532,8 @@
     public void registerBatchedSurfaceControlInputReceiver(int displayId,
             @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
             @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
-        mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+        mGlobal.registerBatchedSurfaceControlInputReceiver(displayId,
+                new InputTransferToken(hostToken),
                 surfaceControl, choreographer, receiver);
     }
 
@@ -539,7 +541,8 @@
     public void registerUnbatchedSurfaceControlInputReceiver(
             int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
             @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
-        mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken,
+        mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId,
+                new InputTransferToken(hostToken),
                 surfaceControl, looper, receiver);
     }
 
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index c4d18c6..3f1ae51 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -23,7 +23,6 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteCallback;
@@ -33,6 +32,7 @@
 import android.view.View.FocusDirection;
 import android.view.WindowInsets.Type.InsetsType;
 import android.window.ClientWindowFrames;
+import android.window.InputTransferToken;
 import android.window.OnBackInvokedCallbackInfo;
 
 import java.util.HashMap;
@@ -60,7 +60,7 @@
         SurfaceControl mLeash;
         Rect mFrame;
         Rect mAttachedFrame;
-        IBinder mInputTransferToken;
+        InputTransferToken mInputTransferToken;
 
         State(SurfaceControl sc, WindowManager.LayoutParams p, int displayId, IWindow client,
                 SurfaceControl leash, Rect frame) {
@@ -90,8 +90,8 @@
     protected final SurfaceControl mRootSurface;
     private final Configuration mConfiguration;
     private final IWindowSession mRealWm;
-    private final IBinder mHostInputToken;
-    private final IBinder mInputTransferToken = new Binder();
+    final InputTransferToken mHostInputTransferToken;
+    private final InputTransferToken mInputTransferToken = new InputTransferToken();
     private InsetsState mInsetsState;
     private final ClientWindowFrames mTmpFrames = new ClientWindowFrames();
     private final MergedConfiguration mTmpConfig = new MergedConfiguration();
@@ -100,18 +100,18 @@
     private ISurfaceControlViewHostParent mParentInterface;
 
     public WindowlessWindowManager(Configuration c, SurfaceControl rootSurface,
-            IBinder hostInputToken) {
+            InputTransferToken hostInputTransferToken) {
         mRootSurface = rootSurface;
         mConfiguration = new Configuration(c);
         mRealWm = WindowManagerGlobal.getWindowSession();
-        mHostInputToken = hostInputToken;
+        mHostInputTransferToken = hostInputTransferToken;
     }
 
     public void setConfiguration(Configuration configuration) {
         mConfiguration.setTo(configuration);
     }
 
-    IBinder getInputTransferToken(IBinder window) {
+    InputTransferToken getInputTransferToken(IBinder window) {
         synchronized (this) {
             // This can only happen if someone requested the focusGrantToken before setView was
             // called for the SCVH. In that case, use the root focusGrantToken since this will be
@@ -211,7 +211,7 @@
             if (mStateForWindow.isEmpty()) {
                 state.mInputTransferToken = mInputTransferToken;
             } else {
-                state.mInputTransferToken = new Binder();
+                state.mInputTransferToken = new InputTransferToken();
             }
 
             mStateForWindow.put(window.asBinder(), state);
@@ -230,15 +230,15 @@
                 if (mRealWm instanceof IWindowSession.Stub) {
                     mRealWm.grantInputChannel(displayId,
                             new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
-                            window.asBinder(), mHostInputToken, attrs.flags, attrs.privateFlags,
-                            attrs.inputFeatures, attrs.type,
-                            attrs.token, state.mInputTransferToken, attrs.getTitle().toString(),
+                            window.asBinder(), mHostInputTransferToken, attrs.flags,
+                            attrs.privateFlags, attrs.inputFeatures, attrs.type, attrs.token,
+                            state.mInputTransferToken, attrs.getTitle().toString(),
                             outInputChannel);
                 } else {
-                    mRealWm.grantInputChannel(displayId, sc, window.asBinder(), mHostInputToken,
-                            attrs.flags, attrs.privateFlags, attrs.inputFeatures, attrs.type,
-                            attrs.token, state.mInputTransferToken, attrs.getTitle().toString(),
-                            outInputChannel);
+                    mRealWm.grantInputChannel(displayId, sc, window.asBinder(),
+                            mHostInputTransferToken, attrs.flags, attrs.privateFlags,
+                            attrs.inputFeatures, attrs.type, attrs.token, state.mInputTransferToken,
+                            attrs.getTitle().toString(), outInputChannel);
                 }
                 state.mInputChannelToken =
                         outInputChannel != null ? outInputChannel.getToken() : null;
@@ -595,9 +595,9 @@
 
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface, IBinder clientToken,
-            IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type,
-            IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
-            InputChannel outInputChannel) {
+            InputTransferToken hostInputToken, int flags, int privateFlags, int inputFeatures,
+            int type, IBinder windowToken, InputTransferToken embeddedInputTransferToken,
+            String inputHandleName, InputChannel outInputChannel) {
     }
 
     @Override
@@ -611,7 +611,7 @@
     }
 
     @Override
-    public void grantEmbeddedWindowFocus(IWindow callingWindow, IBinder targetInputToken,
+    public void grantEmbeddedWindowFocus(IWindow callingWindow, InputTransferToken targetInputToken,
                                          boolean grantFocus) {
     }
 
@@ -659,7 +659,7 @@
 
     @Override
     public boolean transferHostTouchGestureToEmbedded(IWindow hostWindow,
-            IBinder embeddedInputToken) {
+            InputTransferToken embeddedInputToken) {
         Log.e(TAG, "Received request to transferHostTouchGestureToEmbedded on"
                 + " WindowlessWindowManager. We shouldn't get here!");
         return false;
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index d38a95e..b60efc1 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -81,12 +81,21 @@
      * {@link Intent#getAction() Intent action} for IME that
      * {@link #supportsStylusHandwriting() supports stylus handwriting}.
      *
-     * @see #createStylusHandwritingSettingsActivityIntent().
+     * @see #createStylusHandwritingSettingsActivityIntent()
      */
     public static final String ACTION_STYLUS_HANDWRITING_SETTINGS =
             "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS";
 
     /**
+     * {@link Intent#getAction() Intent action} for the IME language settings.
+     *
+     * @see #createImeLanguageSettingsActivityIntent()
+     */
+    @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP)
+    public static final String ACTION_IME_LANGUAGE_SETTINGS =
+            "android.view.inputmethod.action.IME_LANGUAGE_SETTINGS";
+
+    /**
      * Maximal length of a component name
      * @hide
      */
@@ -132,6 +141,13 @@
     final String mSettingsActivityName;
 
     /**
+     * The input method language settings activity's name, used to
+     * launch the language settings activity of this input method.
+     */
+    @Nullable
+    private final String mLanguageSettingsActivityName;
+
+    /**
      * The resource in the input method's .apk that holds a boolean indicating
      * whether it should be considered the default input method for this
      * system.  This is a resource ID instead of the final value so that it
@@ -244,6 +260,7 @@
 
         PackageManager pm = context.getPackageManager();
         String settingsActivityComponent = null;
+        String languageSettingsActivityComponent = null;
         String stylusHandwritingSettingsActivity = null;
         boolean isVrOnly;
         boolean isVirtualDeviceOnly;
@@ -277,9 +294,17 @@
                     com.android.internal.R.styleable.InputMethod);
             settingsActivityComponent = sa.getString(
                     com.android.internal.R.styleable.InputMethod_settingsActivity);
-            if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) || (
-                    settingsActivityComponent != null
-                            && settingsActivityComponent.length() > COMPONENT_NAME_MAX_LENGTH)) {
+            if (Flags.imeSwitcherRevamp()) {
+                languageSettingsActivityComponent = sa.getString(
+                        com.android.internal.R.styleable.InputMethod_languageSettingsActivity);
+            }
+            if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH)
+                    || (settingsActivityComponent != null
+                            && settingsActivityComponent.length()
+                                > COMPONENT_NAME_MAX_LENGTH)
+                    || (languageSettingsActivityComponent != null
+                            && languageSettingsActivityComponent.length()
+                                > COMPONENT_NAME_MAX_LENGTH)) {
                 throw new XmlPullParserException(
                         "Activity name exceeds maximum of 1000 characters");
             }
@@ -382,6 +407,7 @@
         }
         mSubtypes = new InputMethodSubtypeArray(subtypes);
         mSettingsActivityName = settingsActivityComponent;
+        mLanguageSettingsActivityName = languageSettingsActivityComponent;
         mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivity;
         mIsDefaultResId = isDefaultResId;
         mIsAuxIme = isAuxIme;
@@ -401,6 +427,7 @@
     public InputMethodInfo(InputMethodInfo source) {
         mId = source.mId;
         mSettingsActivityName = source.mSettingsActivityName;
+        mLanguageSettingsActivityName = source.mLanguageSettingsActivityName;
         mIsDefaultResId = source.mIsDefaultResId;
         mIsAuxIme = source.mIsAuxIme;
         mSupportsSwitchingToNextInputMethod = source.mSupportsSwitchingToNextInputMethod;
@@ -422,6 +449,7 @@
     InputMethodInfo(Parcel source) {
         mId = source.readString();
         mSettingsActivityName = source.readString();
+        mLanguageSettingsActivityName = source.readString8();
         mIsDefaultResId = source.readInt();
         mIsAuxIme = source.readInt() == 1;
         mSupportsSwitchingToNextInputMethod = source.readInt() == 1;
@@ -445,8 +473,9 @@
     public InputMethodInfo(String packageName, String className,
             CharSequence label, String settingsActivity) {
         this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
-                settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
-                false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+                settingsActivity, null /* languageSettingsActivity */, null /* subtypes */,
+                0 /* isDefaultResId */, false /* forceDefault */,
+                true /* supportsSwitchingToNextInputMethod */,
                 false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
                 false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
                 false /* supportsStylusHandwriting */,
@@ -461,11 +490,12 @@
     @TestApi
     public InputMethodInfo(@NonNull String packageName, @NonNull String className,
             @NonNull CharSequence label, @NonNull String settingsActivity,
-            boolean supportStylusHandwriting,
+            @NonNull String languageSettingsActivity, boolean supportStylusHandwriting,
             @NonNull String stylusHandwritingSettingsActivityAttr) {
         this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
-                settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
-                false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+                settingsActivity, languageSettingsActivity, null /* subtypes */,
+                0 /* isDefaultResId */, false /* forceDefault */,
+                true /* supportsSwitchingToNextInputMethod */,
                 false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
                 false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
                 supportStylusHandwriting, stylusHandwritingSettingsActivityAttr,
@@ -481,8 +511,9 @@
             @NonNull CharSequence label, @NonNull String settingsActivity,
             int handledConfigChanges) {
         this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
-                settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
-                false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+                settingsActivity, null /* languageSettingsActivity */, null /* subtypes */,
+                0 /* isDefaultResId */, false /* forceDefault */,
+                true /* supportsSwitchingToNextInputMethod */,
                 false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
                 false /* isVirtualDeviceOnly */, handledConfigChanges,
                 false /* supportsStylusHandwriting */,
@@ -497,7 +528,8 @@
     public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
             String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
             boolean forceDefault) {
-        this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
+        this(ri, isAuxIme, settingsActivity, null /* languageSettingsActivity */, subtypes,
+                isDefaultResId, forceDefault,
                 true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */,
                 false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledconfigChanges */,
                 false /* supportsStylusHandwriting */,
@@ -512,7 +544,8 @@
     public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
             List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
             boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) {
-        this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
+        this(ri, isAuxIme, settingsActivity, null /* languageSettingsActivity */, subtypes,
+                isDefaultResId, forceDefault,
                 supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly,
                 false /* isVirtualDeviceOnly */,
                 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */,
@@ -525,7 +558,8 @@
      * @hide
      */
     public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
-            List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
+            @Nullable String languageSettingsActivity, List<InputMethodSubtype> subtypes,
+            int isDefaultResId, boolean forceDefault,
             boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled,
             boolean isVrOnly, boolean isVirtualDeviceOnly, int handledConfigChanges,
             boolean supportsStylusHandwriting, String stylusHandwritingSettingsActivityAttr,
@@ -534,6 +568,7 @@
         mService = ri;
         mId = new ComponentName(si.packageName, si.name).flattenToShortString();
         mSettingsActivityName = settingsActivity;
+        mLanguageSettingsActivityName = languageSettingsActivity;
         mIsDefaultResId = isDefaultResId;
         mIsAuxIme = isAuxIme;
         mSubtypes = new InputMethodSubtypeArray(subtypes);
@@ -756,9 +791,34 @@
                         mStylusHandwritingSettingsActivityAttr));
     }
 
+    /**
+     * Returns {@link Intent} for IME language settings activity with
+     * {@link Intent#getAction() Intent action} {@link #ACTION_IME_LANGUAGE_SETTINGS},
+     * else <code>null</code> if
+     * {@link android.R.styleable#InputMethod_languageSettingsActivity} is not defined.
+     *
+     * <p>To launch IME language settings, use this method to get the {@link Intent} to launch
+     * the IME language settings activity.</p>
+     * <p>e.g.<pre><code>startActivity(createImeLanguageSettingsActivityIntent());</code></pre></p>
+     *
+     * @attr ref R.styleable#InputMethod_languageSettingsActivity
+     */
+    @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP)
+    @Nullable
+    public Intent createImeLanguageSettingsActivityIntent() {
+        if (TextUtils.isEmpty(mLanguageSettingsActivityName)) {
+            return null;
+        }
+        return new Intent(ACTION_IME_LANGUAGE_SETTINGS).setComponent(
+                new ComponentName(getServiceInfo().packageName,
+                        mLanguageSettingsActivityName)
+        );
+    }
+
     public void dump(Printer pw, String prefix) {
         pw.println(prefix + "mId=" + mId
                 + " mSettingsActivityName=" + mSettingsActivityName
+                + " mLanguageSettingsActivityName=" + mLanguageSettingsActivityName
                 + " mIsVrOnly=" + mIsVrOnly
                 + " mIsVirtualDeviceOnly=" + mIsVirtualDeviceOnly
                 + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod
@@ -779,8 +839,9 @@
     @Override
     public String toString() {
         return "InputMethodInfo{" + mId
-                + ", settings: "
-                + mSettingsActivityName + "}";
+                + ", settings: " + mSettingsActivityName
+                + ", languageSettings: " + mLanguageSettingsActivityName
+                + "}";
     }
 
     /**
@@ -872,6 +933,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mId);
         dest.writeString(mSettingsActivityName);
+        dest.writeString8(mLanguageSettingsActivityName);
         dest.writeInt(mIsDefaultResId);
         dest.writeInt(mIsAuxIme ? 1 : 0);
         dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0);
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index ccc5dbb..7f1cc8e 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -54,3 +54,11 @@
     bug: "293640003"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "ime_switcher_revamp"
+    namespace: "input_method"
+    description: "Feature flag for revamping the Input Method Switcher menu"
+    bug: "311791923"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/window/InputTransferToken.aidl b/core/java/android/window/InputTransferToken.aidl
new file mode 100644
index 0000000..c6844b8
--- /dev/null
+++ b/core/java/android/window/InputTransferToken.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.window;
+
+/** @hide */
+parcelable InputTransferToken;
diff --git a/core/java/android/window/InputTransferToken.java b/core/java/android/window/InputTransferToken.java
new file mode 100644
index 0000000..0601b2a
--- /dev/null
+++ b/core/java/android/window/InputTransferToken.java
@@ -0,0 +1,104 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControlViewHost;
+
+import java.util.Objects;
+
+/**
+ * A token that can be used to request focus on or to transfer touch gesture to a
+ * {@link SurfaceControlViewHost} or {@link android.view.SurfaceControl} that has an input channel.
+ * @hide
+ */
+public final class InputTransferToken implements Parcelable {
+    /**
+     * @hide
+     */
+    @NonNull
+    public final IBinder mToken;
+
+    /**
+     * @hide
+     */
+    public InputTransferToken(@NonNull IBinder token) {
+        mToken = token;
+    }
+
+    /**
+     * @hide
+     */
+    public InputTransferToken() {
+        mToken = new Binder();
+    }
+
+    private InputTransferToken(Parcel in) {
+        mToken = in.readStrongBinder();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mToken);
+    }
+
+    public static final @NonNull Creator<InputTransferToken> CREATOR = new Creator<>() {
+        public InputTransferToken createFromParcel(Parcel in) {
+            return new InputTransferToken(in);
+        }
+
+        public InputTransferToken[] newArray(int size) {
+            return new InputTransferToken[size];
+        }
+    };
+
+
+    /**
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mToken);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        InputTransferToken other = (InputTransferToken) obj;
+        return other.mToken == mToken;
+    }
+
+}
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 473b814..31e3a34 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -40,6 +40,7 @@
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.AttachedSurfaceControl;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.SurfaceControlViewHost;
@@ -332,9 +333,11 @@
                                     + Thread.currentThread().getId());
                 }
 
+                AttachedSurfaceControl attachedSurfaceControl = surfaceView.getRootSurfaceControl();
                 SurfaceControlViewHost viewHost = new SurfaceControlViewHost(viewContext,
                         viewContext.getDisplay(),
-                        surfaceView.getHostToken(),
+                        attachedSurfaceControl == null ? null
+                                : attachedSurfaceControl.getInputTransferToken(),
                         "SplashScreenView");
                 ImageView imageView = new ImageView(viewContext);
                 imageView.setBackground(mIconDrawable);
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index feae173..15b9b78 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -159,8 +159,11 @@
      */
     public static final int FLAG_SYNC = 1 << 21;
 
+    /** This change represents its start configuration for the duration of the animation. */
+    public static final int FLAG_CONFIG_AT_END = 1 << 22;
+
     /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
-    public static final int FLAG_FIRST_CUSTOM = 1 << 22;
+    public static final int FLAG_FIRST_CUSTOM = 1 << 23;
 
     /** The change belongs to a window that won't contain activities. */
     public static final int FLAGS_IS_NON_APP_WINDOW =
@@ -193,6 +196,7 @@
             FLAG_TASK_LAUNCHING_BEHIND,
             FLAG_MOVED_TO_TOP,
             FLAG_SYNC,
+            FLAG_CONFIG_AT_END,
             FLAG_FIRST_CUSTOM
     })
     public @interface ChangeFlags {}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index d9b5b2d..efc71d7 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -914,6 +914,23 @@
     }
 
     /**
+     * Defers client-facing configuration changes for activities in `container` until the end of
+     * the transition animation. The configuration will still be applied to the WMCore hierarchy
+     * at the normal time (beginning); so, special consideration must be made for this in the
+     * animation.
+     *
+     * @param container WindowContainerToken who's children should defer config notification.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction deferConfigToTransitionEnd(
+            @NonNull WindowContainerToken container) {
+        final Change change = getOrCreateChange(container.asBinder());
+        change.mConfigAtTransitionEnd = true;
+        return this;
+    }
+
+    /**
      * Merges another WCT into this one.
      * @param transfer When true, this will transfer everything from other potentially leaving
      *                 other in an unusable state. When false, other is left alone, but
@@ -1050,6 +1067,7 @@
         private Rect mBoundsChangeSurfaceBounds = null;
         @Nullable
         private Rect mRelativeBounds = null;
+        private boolean mConfigAtTransitionEnd = false;
 
         private int mActivityWindowingMode = -1;
         private int mWindowingMode = -1;
@@ -1082,6 +1100,7 @@
                 mRelativeBounds = new Rect();
                 mRelativeBounds.readFromParcel(in);
             }
+            mConfigAtTransitionEnd = in.readBoolean();
 
             mWindowingMode = in.readInt();
             mActivityWindowingMode = in.readInt();
@@ -1134,6 +1153,8 @@
                         ? other.mRelativeBounds
                         : new Rect(other.mRelativeBounds);
             }
+            mConfigAtTransitionEnd = mConfigAtTransitionEnd
+                    || other.mConfigAtTransitionEnd;
         }
 
         public int getWindowingMode() {
@@ -1191,6 +1212,11 @@
             return mDragResizing;
         }
 
+        /** Gets whether the config should be sent to the client at the end of the transition. */
+        public boolean getConfigAtTransitionEnd() {
+            return mConfigAtTransitionEnd;
+        }
+
         public int getChangeMask() {
             return mChangeMask;
         }
@@ -1269,6 +1295,9 @@
             if ((mChangeMask & CHANGE_RELATIVE_BOUNDS) != 0) {
                 sb.append("relativeBounds:").append(mRelativeBounds).append(",");
             }
+            if (mConfigAtTransitionEnd) {
+                sb.append("configAtTransitionEnd").append(",");
+            }
             sb.append("}");
             return sb.toString();
         }
@@ -1297,6 +1326,7 @@
             if (mRelativeBounds != null) {
                 mRelativeBounds.writeToParcel(dest, flags);
             }
+            dest.writeBoolean(mConfigAtTransitionEnd);
 
             dest.writeInt(mWindowingMode);
             dest.writeInt(mActivityWindowingMode);
diff --git a/core/java/com/android/internal/os/BackgroundThread.java b/core/java/com/android/internal/os/BackgroundThread.java
index 72da819..b75daed 100644
--- a/core/java/com/android/internal/os/BackgroundThread.java
+++ b/core/java/com/android/internal/os/BackgroundThread.java
@@ -27,6 +27,7 @@
 /**
  * Shared singleton background thread for each process.
  */
[email protected]
 public final class BackgroundThread extends HandlerThread {
     private static final long SLOW_DISPATCH_THRESHOLD_MS = 10_000;
     private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000;
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index aa60cc9..0b7593a 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -297,9 +297,20 @@
         }
     }
 
+    public static class EventLogger {
+        /**
+         * Records a statsd event when the batterystats config file is written to disk.
+         */
+        public void writeCommitSysConfigFile(long startTimeMs) {
+            com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
+                    "batterystats", SystemClock.uptimeMillis() - startTimeMs);
+        }
+    }
+
     private TraceDelegate mTracer;
     private int mTraceLastState = 0;
     private int mTraceLastState2 = 0;
+    private final EventLogger mEventLogger;
 
     /**
      * Constructor
@@ -311,8 +322,16 @@
     public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
             HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
             MonotonicClock monotonicClock) {
+        this(systemDir, maxHistoryFiles, maxHistoryBufferSize,
+                stepDetailsCalculator, clock, monotonicClock, new TraceDelegate(),
+                new EventLogger());
+    }
+
+    public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock, TraceDelegate tracer, EventLogger eventLogger) {
         this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
-                stepDetailsCalculator, clock, monotonicClock, new TraceDelegate());
+                stepDetailsCalculator, clock, monotonicClock, tracer, eventLogger);
         initHistoryBuffer();
     }
 
@@ -320,15 +339,15 @@
     public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
             int maxHistoryFiles, int maxHistoryBufferSize,
             HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
-            MonotonicClock monotonicClock, TraceDelegate tracer) {
+            MonotonicClock monotonicClock, TraceDelegate tracer, EventLogger eventLogger) {
         this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator,
-                clock, monotonicClock, tracer, null);
+                clock, monotonicClock, tracer, eventLogger, null);
     }
 
     private BatteryStatsHistory(Parcel historyBuffer, File systemDir,
             int maxHistoryFiles, int maxHistoryBufferSize,
             HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
-            MonotonicClock monotonicClock, TraceDelegate tracer,
+            MonotonicClock monotonicClock, TraceDelegate tracer, EventLogger eventLogger,
             BatteryStatsHistory writableHistory) {
         mHistoryBuffer = historyBuffer;
         mSystemDir = systemDir;
@@ -338,6 +357,7 @@
         mTracer = tracer;
         mClock = clock;
         mMonotonicClock = monotonicClock;
+        mEventLogger = eventLogger;
         mWritableHistory = writableHistory;
         if (mWritableHistory != null) {
             mMutable = false;
@@ -394,19 +414,21 @@
             HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
             MonotonicClock monotonicClock) {
         this(maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator, clock, monotonicClock,
-                new TraceDelegate());
+                new TraceDelegate(), new EventLogger());
     }
 
     @VisibleForTesting
     public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
             HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
-            MonotonicClock monotonicClock, TraceDelegate traceDelegate) {
+            MonotonicClock monotonicClock, TraceDelegate traceDelegate,
+            EventLogger eventLogger) {
         mMaxHistoryFiles = maxHistoryFiles;
         mMaxHistoryBufferSize = maxHistoryBufferSize;
         mStepDetailsCalculator = stepDetailsCalculator;
         mTracer = traceDelegate;
         mClock = clock;
         mMonotonicClock = monotonicClock;
+        mEventLogger = eventLogger;
 
         mHistoryBuffer = Parcel.obtain();
         mSystemDir = null;
@@ -425,6 +447,7 @@
         mSystemDir = null;
         mHistoryDir = null;
         mStepDetailsCalculator = null;
+        mEventLogger = new EventLogger();
         mWritableHistory = null;
         mMutable = false;
 
@@ -482,7 +505,7 @@
             historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
 
             return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null,
-                    null, this);
+                    null, mEventLogger, this);
         }
     }
 
@@ -2154,8 +2177,7 @@
                         + " duration ms:" + (SystemClock.uptimeMillis() - startTimeMs)
                         + " bytes:" + p.dataSize());
             }
-            com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
-                    "batterystats", SystemClock.uptimeMillis() - startTimeMs);
+            mEventLogger.writeCommitSysConfigFile(startTimeMs);
         } catch (IOException e) {
             Slog.w(TAG, "Error writing battery statistics", e);
             file.failWrite(fos);
@@ -2164,6 +2186,7 @@
         }
     }
 
+
     /**
      * Returns the total number of history tags in the tag pool.
      */
diff --git a/core/java/com/android/internal/os/LongMultiStateCounter.java b/core/java/com/android/internal/os/LongMultiStateCounter.java
index 33a9d54..064609f 100644
--- a/core/java/com/android/internal/os/LongMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongMultiStateCounter.java
@@ -55,11 +55,12 @@
  *
  * @hide
  */
[email protected]
[email protected](
+        "com.android.hoststubgen.nativesubstitution.LongMultiStateCounter_host")
 public final class LongMultiStateCounter implements Parcelable {
 
-    private static final NativeAllocationRegistry sRegistry =
-            NativeAllocationRegistry.createMalloced(
-                    LongMultiStateCounter.class.getClassLoader(), native_getReleaseFunc());
+    private static NativeAllocationRegistry sRegistry;
 
     private final int mStateCount;
 
@@ -71,16 +72,33 @@
         Preconditions.checkArgumentPositive(stateCount, "stateCount must be greater than 0");
         mStateCount = stateCount;
         mNativeObject = native_init(stateCount);
-        sRegistry.registerNativeAllocation(this, mNativeObject);
+        registerNativeAllocation();
     }
 
     private LongMultiStateCounter(Parcel in) {
         mNativeObject = native_initFromParcel(in);
-        sRegistry.registerNativeAllocation(this, mNativeObject);
+        registerNativeAllocation();
 
         mStateCount = native_getStateCount(mNativeObject);
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private void registerNativeAllocation() {
+        if (sRegistry == null) {
+            synchronized (LongMultiStateCounter.class) {
+                if (sRegistry == null) {
+                    sRegistry = NativeAllocationRegistry.createMalloced(
+                            LongMultiStateCounter.class.getClassLoader(), native_getReleaseFunc());
+                }
+            }
+        }
+        sRegistry.registerNativeAllocation(this, mNativeObject);
+    }
+
+    private void registerNativeAllocation$ravenwood() {
+        // No-op under ravenwood
+    }
+
     public int getStateCount() {
         return mStateCount;
     }
@@ -221,10 +239,10 @@
     private static native long native_getCount(long nativeObject, int state);
 
     @FastNative
-    private native String native_toString(long nativeObject);
+    private static native String native_toString(long nativeObject);
 
     @FastNative
-    private native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
+    private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
 
     @FastNative
     private static native long native_initFromParcel(Parcel parcel);
diff --git a/core/java/com/android/internal/power/EnergyConsumerStats.java b/core/java/com/android/internal/power/EnergyConsumerStats.java
index e2098dd..764908d 100644
--- a/core/java/com/android/internal/power/EnergyConsumerStats.java
+++ b/core/java/com/android/internal/power/EnergyConsumerStats.java
@@ -44,6 +44,7 @@
  * This class doesn't use a TimeBase, and instead requires manual decisions about when to
  * accumulate since it is trivial. However, in the future, a TimeBase could be used instead.
  */
[email protected]
 public class EnergyConsumerStats {
     private static final String TAG = "MeasuredEnergyStats";
 
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 8c2a525..f2783c4 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -94,6 +94,8 @@
 
     WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
     WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
+    WM_DEBUG_EMBEDDED_WINDOWS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
     TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index ddf7a67..56d3fbb 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -100,7 +100,7 @@
     return asLongMultiStateCounter(nativePtr)->getCount(state);
 }
 
-static jobject native_toString(JNIEnv *env, jobject self, jlong nativePtr) {
+static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) {
     return env->NewStringUTF(asLongMultiStateCounter(nativePtr)->toString().c_str());
 }
 
@@ -118,7 +118,7 @@
         }                                     \
     }
 
-static void native_writeToParcel(JNIEnv *env, jobject self, jlong nativePtr, jobject jParcel,
+static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel,
                                  jint flags) {
     battery::LongMultiStateCounter *counter = asLongMultiStateCounter(nativePtr);
     ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
diff --git a/core/proto/android/hardware/location/context_hub_info.proto b/core/proto/android/hardware/location/context_hub_info.proto
index de5cd55..95b5a1a 100644
--- a/core/proto/android/hardware/location/context_hub_info.proto
+++ b/core/proto/android/hardware/location/context_hub_info.proto
@@ -46,4 +46,6 @@
     optional float peak_power_draw_mw = 12;
     // The maximum number of bytes that can be sent per message to the hub
     optional int32 max_packet_length_bytes = 13;
+    // Whether reliable messages are supported
+    optional int32 supports_reliable_messages = 14;
 }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 908eeeb..45861a3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3893,6 +3893,10 @@
         <!-- Component name of an activity that allows the user to modify
              the settings for this service. -->
         <attr name="settingsActivity" format="string" />
+        <!-- Component name of an activity that allows the user to modify
+             on-screen keyboards variants (e.g. different language or layout) for this service. -->
+        <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") -->
+        <attr name="languageSettingsActivity" format="string"/>
         <!-- Set to true in all of the configurations for which this input
              method should be considered an option as the default. -->
         <attr name="isDefault" format="boolean" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d4e727e..c6bc589 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2141,6 +2141,12 @@
         <item>com.android.location.fused</item>
     </string-array>
 
+   <!-- Package name of the extension software fallback. -->
+    <string name="config_extensionFallbackPackageName" translatable="false"></string>
+
+   <!-- Service name of the extension software fallback. -->
+    <string name="config_extensionFallbackServiceName" translatable="false"></string>
+
     <!-- Package name(s) of Advanced Driver Assistance applications. These packages have additional
     management of access to location, specific to driving assistance use-cases. They must be system
     packages. This configuration is only applicable to devices that declare
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index d0216b3..c14fe57 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -172,6 +172,35 @@
     <integer name="config_satellite_nb_iot_inactivity_timeout_millis">180000</integer>
     <java-symbol type="integer" name="config_satellite_nb_iot_inactivity_timeout_millis" />
 
+    <!-- The time duration in millis needed to switch the modem image from TN to NTN. -->
+    <integer name="config_satellite_modem_image_switching_duration_millis">20000</integer>
+    <java-symbol type="integer" name="config_satellite_modem_image_switching_duration_millis" />
+
+    <!-- The time duration in millis after which Telephony will abort the datagram sending requests.
+         Telephony starts a timer when receiving a datagram sending request in either OFF, IDLE, or
+         NOT_CONNECTED state. In NOT_CONNECTED, the duration of the timer is given by this config.
+         In OFF or IDLE state, the duration of the timer is the sum of this config and the
+         config_satellite_modem_image_switching_duration_millis.
+         -->
+    <integer name="config_datagram_wait_for_connected_state_timeout_millis">60000</integer>
+    <java-symbol type="integer" name="config_datagram_wait_for_connected_state_timeout_millis" />
+
+    <!-- The time duration in millis after which Telephony will stop waiting for the response of the
+         satellite enable request from modem, and send failure response to the client that has
+         requested Telephony to enable satellite.
+         -->
+    <integer name="config_wait_for_satellite_enabling_response_timeout_millis">180000</integer>
+    <java-symbol type="integer" name="config_wait_for_satellite_enabling_response_timeout_millis" />
+
+    <!-- The time duration in millis after which Telephony will abort the datagram sending requests
+         and send failure response to the client that has requested sending the datagrams. Telephony
+         starts a timer after pushing down the datagram sending request to modem. Before expiry, the
+         timer will be stopped when Telephony receives the response for the sending request from
+         modem.
+         -->
+    <integer name="config_wait_for_datagram_sending_response_timeout_millis">180000</integer>
+    <java-symbol type="integer" name="config_wait_for_datagram_sending_response_timeout_millis" />
+
     <!-- The timeout duration in milliseconds to determine whether to recommend Dialer to show the
          emergency messaging option to users.
 
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index f9cf28c..0a6779a9 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -151,6 +151,8 @@
     <public name="windowOptOutEdgeToEdgeEnforcement"/>
     <!-- @FlaggedApi("android.security.content_uri_permission_apis") -->
     <public name="requireContentUriPermissionFromCaller" />
+    <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp") -->
+    <public name="languageSettingsActivity"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3df7570..3d19c85 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2154,6 +2154,8 @@
   <java-symbol type="string" name="config_systemImageEditor" />
   <java-symbol type="string" name="config_datause_iface" />
   <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
+  <java-symbol type="string" name="config_extensionFallbackPackageName" />
+  <java-symbol type="string" name="config_extensionFallbackServiceName" />
   <java-symbol type="string" name="config_fusedLocationProviderPackageName" />
   <java-symbol type="string" name="config_gnssLocationProviderPackageName" />
   <java-symbol type="string" name="config_geocoderProviderPackageName" />
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
index a3f537e..36ab0d4 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -70,6 +70,9 @@
         assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(false));
         assertThat(imi.supportsStylusHandwriting(), is(false));
         assertThat(imi.createStylusHandwritingSettingsActivityIntent(), equalTo(null));
+        if (Flags.imeSwitcherRevamp()) {
+            assertThat(imi.createImeLanguageSettingsActivityIntent(), equalTo(null));
+        }
     }
 
     @Test
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index ee1a4ac..861f719 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -217,6 +217,7 @@
         "src/android/os/**/*.java",
         "src/android/telephony/PinResultTest.java",
         "src/android/util/**/*.java",
+        "src/android/view/DisplayTest.java",
         "src/android/view/DisplayInfoTest.java",
         "src/com/android/internal/logging/**/*.java",
         "src/com/android/internal/os/**/*.java",
diff --git a/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java b/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java
new file mode 100644
index 0000000..e12ca24a
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothBatteryStatsTest {
+
+    @Test
+    public void parcelability() {
+        BluetoothBatteryStats stats = new BluetoothBatteryStats(List.of(
+                new BluetoothBatteryStats.UidStats(42, 100, 200, 300, 400, 500),
+                new BluetoothBatteryStats.UidStats(99, 600, 700, 800, 900, 999)
+        ));
+
+        Parcel parcel = Parcel.obtain();
+        stats.writeToParcel(parcel, 0);
+        byte[] bytes = parcel.marshall();
+        parcel.recycle();
+
+        parcel = Parcel.obtain();
+        parcel.unmarshall(bytes, 0, bytes.length);
+        parcel.setDataPosition(0);
+
+        BluetoothBatteryStats actual = new BluetoothBatteryStats(parcel);
+
+        assertThat(actual.getUidStats()).hasSize(2);
+
+        BluetoothBatteryStats.UidStats uid1 = actual.getUidStats().stream()
+                .filter(s->s.uid == 42).findFirst().get();
+
+        assertThat(uid1.scanTimeMs).isEqualTo(100);
+        assertThat(uid1.unoptimizedScanTimeMs).isEqualTo(200);
+        assertThat(uid1.scanResultCount).isEqualTo(300);
+        assertThat(uid1.rxTimeMs).isEqualTo(400);
+        assertThat(uid1.txTimeMs).isEqualTo(500);
+
+        BluetoothBatteryStats.UidStats uid2 = actual.getUidStats().stream()
+                .filter(s->s.uid == 99).findFirst().get();
+        assertThat(uid2.scanTimeMs).isEqualTo(600);
+    }
+}
diff --git a/core/tests/coretests/src/android/os/WakeLockStatsTest.java b/core/tests/coretests/src/android/os/WakeLockStatsTest.java
new file mode 100644
index 0000000..2675ba0
--- /dev/null
+++ b/core/tests/coretests/src/android/os/WakeLockStatsTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class WakeLockStatsTest {
+
+    @Test
+    public void parcelablity() {
+        WakeLockStats wakeLockStats = new WakeLockStats(
+                List.of(new WakeLockStats.WakeLock(1, "foo", 200, 3000, 40000),
+                        new WakeLockStats.WakeLock(2, "bar", 500, 6000, 70000)));
+
+        Parcel parcel = Parcel.obtain();
+        wakeLockStats.writeToParcel(parcel, 0);
+        byte[] bytes = parcel.marshall();
+        parcel.recycle();
+
+        parcel = Parcel.obtain();
+        parcel.unmarshall(bytes, 0, bytes.length);
+        parcel.setDataPosition(0);
+
+        WakeLockStats actual = WakeLockStats.CREATOR.createFromParcel(parcel);
+        assertThat(actual.getWakeLocks()).hasSize(2);
+        WakeLockStats.WakeLock wl1 = actual.getWakeLocks().get(0);
+        assertThat(wl1.uid).isEqualTo(1);
+        assertThat(wl1.name).isEqualTo("foo");
+        assertThat(wl1.timesAcquired).isEqualTo(200);
+        assertThat(wl1.totalTimeHeldMs).isEqualTo(3000);
+        assertThat(wl1.timeHeldMs).isEqualTo(40000);
+
+        WakeLockStats.WakeLock wl2 = actual.getWakeLocks().get(1);
+        assertThat(wl2.uid).isEqualTo(2);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/DisplayTest.java b/core/tests/coretests/src/android/view/DisplayTest.java
new file mode 100644
index 0000000..4d2a1c4
--- /dev/null
+++ b/core/tests/coretests/src/android/view/DisplayTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.view;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.util.DebugUtils;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Test;
+
+import java.util.function.IntFunction;
+
+@SmallTest
+public class DisplayTest {
+    private static final int[] DISPLAY_STATES = {
+            Display.STATE_UNKNOWN,
+            Display.STATE_OFF,
+            Display.STATE_ON,
+            Display.STATE_DOZE,
+            Display.STATE_DOZE_SUSPEND,
+            Display.STATE_VR,
+            Display.STATE_ON_SUSPEND
+    };
+
+    @Test
+    public void isSuspendedState() {
+        assertOnlyTrueForStates(
+                Display::isSuspendedState,
+                Display.STATE_OFF,
+                Display.STATE_DOZE_SUSPEND,
+                Display.STATE_ON_SUSPEND
+        );
+    }
+
+    @Test
+    public void isDozeState() {
+        assertOnlyTrueForStates(
+                Display::isDozeState,
+                Display.STATE_DOZE,
+                Display.STATE_DOZE_SUSPEND
+        );
+    }
+
+    @Test
+    public void isActiveState() {
+        assertOnlyTrueForStates(
+                Display::isActiveState,
+                Display.STATE_ON,
+                Display.STATE_VR
+        );
+    }
+
+    @Test
+    public void isOffState() {
+        assertOnlyTrueForStates(
+                Display::isOffState,
+                Display.STATE_OFF
+        );
+    }
+
+    @Test
+    public void isOnState() {
+        assertOnlyTrueForStates(
+                Display::isOnState,
+                Display.STATE_ON,
+                Display.STATE_VR,
+                Display.STATE_ON_SUSPEND
+        );
+    }
+
+    private void assertOnlyTrueForStates(IntFunction<Boolean> function, int... trueStates) {
+        for (int state : DISPLAY_STATES) {
+            boolean actual = function.apply(state);
+            boolean expected = ArrayUtils.contains(trueStates, state);
+            assertWithMessage("Unexpected return for Display.STATE_"
+                    + DebugUtils.constantToString(Display.class, "STATE_", state))
+                    .that(actual).isEqualTo(expected);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 2d117f7..0657e4b9 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -355,16 +355,14 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_GET_HOST_TOKEN_API)
+    @RequiresFlagsEnabled(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
     public void whenViewIsAttachedToWindow_getHostToken() {
         View view = new View(sContext);
         attachViewToWindow(view);
 
         mViewRootImpl = view.getViewRootImpl();
 
-        assertThat(mViewRootImpl.getHostToken()).isNotEqualTo(null);
-        assertThat(mViewRootImpl.getHostToken())
-                .isEqualTo(mViewRootImpl.getInputToken());
+        assertThat(mViewRootImpl.getInputTransferToken()).isNotEqualTo(null);
     }
 
     /**
diff --git a/core/tests/coretests/src/com/android/internal/os/BackgroundThreadTest.java b/core/tests/coretests/src/com/android/internal/os/BackgroundThreadTest.java
new file mode 100644
index 0000000..8bdf4c6
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BackgroundThreadTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.Executor;
+
+public class BackgroundThreadTest {
+
+    @Rule
+    public final RavenwoodRule mRavenwood =
+            new RavenwoodRule.Builder().setProvideMainThread(true).build();
+
+    @Test
+    public void test_get() {
+        BackgroundThread thread = BackgroundThread.get();
+        assertThat(thread.getLooper()).isNotEqualTo(Looper.getMainLooper());
+    }
+
+    @Test
+    public void test_getHandler() {
+        Handler handler = BackgroundThread.getHandler();
+        ConditionVariable done = new ConditionVariable();
+        handler.post(done::open);
+        boolean success = done.block(5000);
+        assertThat(success).isTrue();
+    }
+
+    @Test
+    public void test_getExecutor() {
+        Executor executor = BackgroundThread.getExecutor();
+        ConditionVariable done = new ConditionVariable();
+        executor.execute(done::open);
+        boolean success = done.block(5000);
+        assertThat(success).isTrue();
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
index e064e74..78ef92b 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
@@ -22,7 +22,6 @@
 
 import android.os.BadParcelableException;
 import android.os.Parcel;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
@@ -34,7 +33,6 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-@IgnoreUnderRavenwood(blockedBy = LongMultiStateCounterTest.class)
 public class LongMultiStateCounterTest {
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
diff --git a/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java b/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java
index ae2ef0cb..9c337d7 100644
--- a/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java
@@ -34,7 +34,6 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.view.Display;
 
@@ -49,7 +48,6 @@
  * Test class for {@link EnergyConsumerStats}.
  */
 @SmallTest
-@IgnoreUnderRavenwood(reason = "Needs kernel support")
 public class EnergyConsumerStatsTest {
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0baaff0..d8713f7a 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,8 @@
         <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
         <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
         <permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+        <!-- Permission required for CTS test - PackageManagerTest -->
+        <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index da91a96..c8cbb98 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2185,6 +2185,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-195654020": {
+      "message": "Attempt to transfer touch gesture with host window not associated with embedded window",
+      "level": "WARN",
+      "group": "WM_DEBUG_EMBEDDED_WINDOWS",
+      "at": "com\/android\/server\/wm\/EmbeddedWindowController.java"
+    },
     "-193782861": {
       "message": "Final remove of window: %s",
       "level": "VERBOSE",
@@ -2245,12 +2251,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/Task.java"
     },
-    "-134091882": {
-      "message": "Screenshotting Activity %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_BACK_PREVIEW",
-      "at": "com\/android\/server\/wm\/TaskFragment.java"
-    },
     "-125383273": {
       "message": "Content Recording: waiting to record, so do nothing",
       "level": "VERBOSE",
@@ -3235,6 +3235,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "701366755": {
+      "message": "Attempt to transfer touch gesture with non-existent embedded window",
+      "level": "WARN",
+      "group": "WM_DEBUG_EMBEDDED_WINDOWS",
+      "at": "com\/android\/server\/wm\/EmbeddedWindowController.java"
+    },
     "704998117": {
       "message": "Failed to create surface control for %s",
       "level": "WARN",
@@ -3583,6 +3589,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1042363394": {
+      "message": "Attempt to transfer touch gesture using a host window with no input channel",
+      "level": "WARN",
+      "group": "WM_DEBUG_EMBEDDED_WINDOWS",
+      "at": "com\/android\/server\/wm\/EmbeddedWindowController.java"
+    },
     "1046228706": {
       "message": "Defer transition id=%d for TaskFragmentTransaction=%s",
       "level": "VERBOSE",
@@ -4075,6 +4087,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
+    "1520642640": {
+      "message": "Attempt to transfer touch gesture using embedded window with no associated host",
+      "level": "WARN",
+      "group": "WM_DEBUG_EMBEDDED_WINDOWS",
+      "at": "com\/android\/server\/wm\/EmbeddedWindowController.java"
+    },
     "1521476038": {
       "message": "Attempted to set flag to a display that does not exist: %d",
       "level": "WARN",
@@ -4249,6 +4267,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1721036256": {
+      "message": "Attempt to transfer touch gesture using embedded window that has no input channel",
+      "level": "WARN",
+      "group": "WM_DEBUG_EMBEDDED_WINDOWS",
+      "at": "com\/android\/server\/wm\/EmbeddedWindowController.java"
+    },
     "1730300180": {
       "message": "PendingStartTransaction found",
       "level": "VERBOSE",
@@ -4731,6 +4755,9 @@
     "WM_DEBUG_DREAM": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_EMBEDDED_WINDOWS": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_FOCUS": {
       "tag": "WindowManager"
     },
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c5bc9eb..35c1e8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -3521,7 +3521,14 @@
      */
     void onVerticalOffsetChanged(int offset) {
         // adjust dismiss view vertical position, so that it is still visible to the user
-        mDismissView.setPadding(/* left = */ 0, /* top = */ 0, /* right = */ 0, offset);
+        ViewGroup.LayoutParams lp = mDismissView.getLayoutParams();
+        if (lp instanceof FrameLayout.LayoutParams) {
+            FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) lp;
+            layoutParams.bottomMargin = offset;
+            mDismissView.setLayoutParams(layoutParams);
+        }
+        mMagneticTarget.setScreenVerticalOffset(offset);
+        mMagneticTarget.updateLocationOnScreen();
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 1c74f41..e4cf6d13 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -49,6 +49,7 @@
 import android.view.WindowlessWindowManager;
 import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
+import android.window.InputTransferToken;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -196,7 +197,7 @@
     /**
      * Gets a token associated with the view that can be used to grant the view focus.
      */
-    public IBinder getFocusGrantToken(View view) {
+    public InputTransferToken getFocusGrantToken(View view) {
         SurfaceControlViewHost root = mViewRoots.get(view);
         if (root == null) {
             Slog.e(TAG, "Couldn't get focus grant token since view does not exist in "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index aac1d062..7c931df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -352,8 +352,8 @@
 
         val targetObjectIsInMagneticFieldOf = associatedTargets.firstOrNull { target ->
             val distanceFromTargetCenter = hypot(
-                    ev.rawX - target.centerOnScreen.x,
-                    ev.rawY - target.centerOnScreen.y)
+                    ev.rawX - target.centerOnDisplayX(),
+                    ev.rawY - target.centerOnDisplayY())
             distanceFromTargetCenter < target.magneticFieldRadiusPx
         }
 
@@ -406,7 +406,6 @@
 
         // First, check for relevant gestures concluding with an ACTION_UP.
         if (ev.action == MotionEvent.ACTION_UP) {
-
             velocityTracker.computeCurrentVelocity(1000 /* units */)
             val velX = velocityTracker.xVelocity
             val velY = velocityTracker.yVelocity
@@ -542,7 +541,7 @@
         // Whether velocity is sufficient, depending on whether we're flinging into a target at the
         // top or the bottom of the screen.
         val velocitySufficient =
-                if (rawY < target.centerOnScreen.y) velY > flingToTargetMinVelocity
+                if (rawY < target.centerOnDisplayY()) velY > flingToTargetMinVelocity
                 else velY < flingToTargetMinVelocity
 
         if (!velocitySufficient) {
@@ -560,15 +559,15 @@
             val yIntercept = rawY - slope * rawX
 
             // ...calculate the x value when y = the target's y-coordinate.
-            targetCenterXIntercept = (target.centerOnScreen.y - yIntercept) / slope
+            targetCenterXIntercept = (target.centerOnDisplayY() - yIntercept) / slope
         }
 
         // The width of the area we're looking for a fling towards.
         val targetAreaWidth = target.targetView.width * flingToTargetWidthPercent
 
         // Velocity was sufficient, so return true if the intercept is within the target area.
-        return targetCenterXIntercept > target.centerOnScreen.x - targetAreaWidth / 2 &&
-                targetCenterXIntercept < target.centerOnScreen.x + targetAreaWidth / 2
+        return targetCenterXIntercept > target.centerOnDisplayX() - targetAreaWidth / 2 &&
+                targetCenterXIntercept < target.centerOnDisplayX() + targetAreaWidth / 2
     }
 
     /** Cancel animations on this object's x/y properties. */
@@ -601,6 +600,22 @@
     ) {
         val centerOnScreen = PointF()
 
+        /**
+         * Set screen vertical offset amount.
+         *
+         * Screen surface may be vertically shifted in some cases, for example when one-handed mode
+         * is enabled. [MagneticTarget] and [MagnetizedObject] set their location in screen
+         * coordinates (see [MagneticTarget.centerOnScreen] and
+         * [MagnetizedObject.getLocationOnScreen] respectively).
+         *
+         * When a [MagnetizedObject] is dragged, the touch location is determined by
+         * [MotionEvent.getRawX] and [MotionEvent.getRawY]. These work in display coordinates. When
+         * screen is shifted due to one-handed mode, display coordinates and screen coordinates do
+         * not match. To determine if a [MagnetizedObject] is dragged into a [MagneticTarget], view
+         * location on screen is translated to display coordinates using this offset value.
+         */
+        var screenVerticalOffset: Int = 0
+
         private val tempLoc = IntArray(2)
 
         fun updateLocationOnScreen() {
@@ -614,6 +629,23 @@
                         tempLoc[1] + targetView.height / 2f - targetView.translationY)
             }
         }
+
+        /**
+         * Get target center coordinate on x-axis on display. [centerOnScreen] has to be up to date
+         * by calling [updateLocationOnScreen] first.
+         */
+        fun centerOnDisplayX(): Float {
+            return centerOnScreen.x
+        }
+
+        /**
+         * Get target center coordinate on y-axis on display. [centerOnScreen] has to be up to date
+         * by calling [updateLocationOnScreen] first. Use [screenVerticalOffset] to update the
+         * screen offset compared to the display.
+         */
+        fun centerOnDisplayY(): Float {
+            return centerOnScreen.y + screenVerticalOffset
+        }
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index d902621..e83e5d1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -49,6 +49,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerGlobal;
+import android.window.InputTransferToken;
 
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
@@ -74,7 +75,7 @@
 
     private final IBinder mClientToken;
 
-    private final IBinder mFocusGrantToken;
+    private final InputTransferToken mInputTransferToken;
     private final SurfaceControl mDecorationSurface;
     private final InputChannel mInputChannel;
     private final TaskResizeInputEventReceiver mInputEventReceiver;
@@ -121,7 +122,7 @@
         mDecorationSurface = decorationSurface;
         mDisplayController = displayController;
         mClientToken = new Binder();
-        mFocusGrantToken = new Binder();
+        mInputTransferToken = new InputTransferToken();
         mInputChannel = new InputChannel();
         try {
             mWindowSession.grantInputChannel(
@@ -134,7 +135,7 @@
                     INPUT_FEATURE_SPY,
                     TYPE_APPLICATION,
                     null /* windowToken */,
-                    mFocusGrantToken,
+                    mInputTransferToken,
                     TAG + " of " + decorationSurface.toString(),
                     mInputChannel);
         } catch (RemoteException e) {
@@ -169,7 +170,7 @@
                     INPUT_FEATURE_NO_INPUT_CHANNEL,
                     TYPE_INPUT_CONSUMER,
                     null /* windowToken */,
-                    mFocusGrantToken,
+                    mInputTransferToken,
                     "TaskInputSink of " + decorationSurface,
                     mSinkInputChannel);
         } catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
index 9f1ee6c..a9f054e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
@@ -30,6 +30,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.Mockito
 import org.mockito.Mockito.`when`
@@ -97,7 +98,7 @@
 
         // The mock target view will pretend that it's 200x200, and at (400, 800). This means it's
         // occupying the bounds (400, 800, 600, 1000) and it has a center of (500, 900).
-        `when`(targetView.width).thenReturn(targetSize)  // width = 200
+        `when`(targetView.width).thenReturn(targetSize) // width = 200
         `when`(targetView.height).thenReturn(targetSize) // height = 200
         doAnswer { invocation ->
             (invocation.arguments[0] as IntArray).also { location ->
@@ -275,11 +276,11 @@
         // Forcefully fling the object towards the target (but never touch the magnetic field).
         dispatchMotionEvents(
                 getMotionEvent(
-                        x = targetCenterX,
+                        x = 0,
                         y = 0,
                         action = MotionEvent.ACTION_DOWN),
                 getMotionEvent(
-                        x = targetCenterX,
+                        x = targetCenterX / 2,
                         y = targetCenterY / 2),
                 getMotionEvent(
                         x = targetCenterX,
@@ -405,15 +406,78 @@
         verify(magnetListener).onStuckToTarget(magneticTarget)
     }
 
+    @Test
+    fun testMagneticTargetHasScreenOffset_moveIntoAndReleaseInTarget() {
+        magneticTarget.screenVerticalOffset = 500
+
+        dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = targetCenterY))
+        // Moved into the target location, but it should be shifted due to screen offset.
+        // Should not get stuck.
+        verify(magnetListener, never()).onStuckToTarget(magneticTarget)
+
+        dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = targetCenterY + 500))
+        verify(magnetListener).onStuckToTarget(magneticTarget)
+
+        dispatchMotionEvents(
+            getMotionEvent(
+                x = targetCenterX,
+                y = targetCenterY + 500,
+                action = MotionEvent.ACTION_UP
+            )
+        )
+
+        verify(magnetListener).onReleasedInTarget(magneticTarget)
+        verifyNoMoreInteractions(magnetListener)
+    }
+
+    @Test
+    fun testMagneticTargetHasScreenOffset_screenOffsetUpdates() {
+        magneticTarget.screenVerticalOffset = 500
+        val adjustedTargetCenter = targetCenterY + 500
+
+        dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = adjustedTargetCenter))
+        dispatchMotionEvents(getMotionEvent(x = 0, y = 0))
+        verify(magnetListener).onStuckToTarget(magneticTarget)
+        verify(magnetListener)
+                .onUnstuckFromTarget(eq(magneticTarget), anyFloat(), anyFloat(), anyBoolean())
+
+        // Offset if removed, we should now get stuck at the target location
+        magneticTarget.screenVerticalOffset = 0
+        dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = targetCenterY))
+        verify(magnetListener, times(2)).onStuckToTarget(magneticTarget)
+    }
+
+    @Test
+    fun testMagneticTargetHasScreenOffset_flingTowardsTarget() {
+        timeStep = 10
+
+        magneticTarget.screenVerticalOffset = 500
+        val adjustedTargetCenter = targetCenterY + 500
+
+        // Forcefully fling the object towards the target (but never touch the magnetic field).
+        dispatchMotionEvents(
+            getMotionEvent(x = 0, y = 0, action = MotionEvent.ACTION_DOWN),
+            getMotionEvent(x = targetCenterX / 2, y = adjustedTargetCenter / 2),
+            getMotionEvent(
+                x = targetCenterX,
+                y = adjustedTargetCenter - magneticFieldRadius * 2,
+                action = MotionEvent.ACTION_UP
+            )
+        )
+
+        // Nevertheless it should have ended up stuck to the target.
+        verify(magnetListener, times(1)).onStuckToTarget(magneticTarget)
+    }
+
     private fun getSecondMagneticTarget(): MagnetizedObject.MagneticTarget {
         // The first target view is at bounds (400, 800, 600, 1000) and it has a center of
         // (500, 900). We'll add a second one at bounds (0, 800, 200, 1000) with center (100, 900).
         val secondTargetView = mock(View::class.java)
-        var secondTargetCenterX = 100
-        var secondTargetCenterY = 900
+        val secondTargetCenterX = 100
+        val secondTargetCenterY = 900
 
         `when`(secondTargetView.context).thenReturn(context)
-        `when`(secondTargetView.width).thenReturn(targetSize)  // width = 200
+        `when`(secondTargetView.width).thenReturn(targetSize) // width = 200
         `when`(secondTargetView.height).thenReturn(targetSize) // height = 200
         doAnswer { invocation ->
             (invocation.arguments[0] as Runnable).run()
diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h
index 9e6cc81..0939af4 100644
--- a/libs/input/SpriteIcon.h
+++ b/libs/input/SpriteIcon.h
@@ -27,33 +27,20 @@
  * Icon that a sprite displays, including its hotspot.
  */
 struct SpriteIcon {
-    inline SpriteIcon() : style(PointerIconStyle::TYPE_NULL), hotSpotX(0), hotSpotY(0) {}
-    inline SpriteIcon(const graphics::Bitmap& bitmap, PointerIconStyle style, float hotSpotX,
-                      float hotSpotY, bool drawNativeDropShadow)
+    explicit SpriteIcon() = default;
+    explicit SpriteIcon(const graphics::Bitmap& bitmap, PointerIconStyle style, float hotSpotX,
+                        float hotSpotY, bool drawNativeDropShadow)
           : bitmap(bitmap),
             style(style),
             hotSpotX(hotSpotX),
             hotSpotY(hotSpotY),
             drawNativeDropShadow(drawNativeDropShadow) {}
 
-    graphics::Bitmap bitmap;
-    PointerIconStyle style;
-    float hotSpotX;
-    float hotSpotY;
-    bool drawNativeDropShadow;
-
-    inline SpriteIcon copy() const {
-        return SpriteIcon(bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888), style, hotSpotX, hotSpotY,
-                          drawNativeDropShadow);
-    }
-
-    inline void reset() {
-        bitmap.reset();
-        style = PointerIconStyle::TYPE_NULL;
-        hotSpotX = 0;
-        hotSpotY = 0;
-        drawNativeDropShadow = false;
-    }
+    graphics::Bitmap bitmap{};
+    PointerIconStyle style{PointerIconStyle::TYPE_NULL};
+    float hotSpotX{};
+    float hotSpotY{};
+    bool drawNativeDropShadow{false};
 
     inline bool isValid() const { return bitmap.isValid() && !bitmap.isEmpty(); }
 
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 04e99ea..7727078 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -27,7 +27,6 @@
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
 import android.os.UserHandle;
-
 /**
  * {@hide}
  */
@@ -56,6 +55,7 @@
 
     void registerRouter2(IMediaRouter2 router, String packageName);
     void unregisterRouter2(IMediaRouter2 router);
+    void updateScanningStateWithRouter2(IMediaRouter2 router, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState);
     void setDiscoveryRequestWithRouter2(IMediaRouter2 router,
             in RouteDiscoveryPreference preference);
     void setRouteListingPreference(IMediaRouter2 router,
@@ -81,8 +81,7 @@
     void unregisterManager(IMediaRouter2Manager manager);
     void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
             in MediaRoute2Info route, int volume);
-    void startScan(IMediaRouter2Manager manager);
-    void stopScan(IMediaRouter2Manager manager);
+    void updateScanningState(IMediaRouter2Manager manager, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState);
 
     void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
             in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route,
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 88b9643..b0daea8 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -18,7 +18,10 @@
 
 import static android.media.Utils.intersectSortedDistinctRanges;
 import static android.media.Utils.sortDistinctRanges;
+import static android.media.codec.Flags.FLAG_DYNAMIC_COLOR_ASPECTS;
+import static android.media.codec.Flags.FLAG_HLG_EDITING;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -692,6 +695,52 @@
         public static final String FEATURE_HdrEditing = "hdr-editing";
 
         /**
+         * <b>video encoder only</b>: codec supports HLG editing.
+         * <p>
+         * HLG editing support means that the codec accepts 10-bit HDR
+         * input surface in both YUV and RGB pixel format. This feature
+         * is only meaningful when using a 10-bit (HLG) profile and
+         * 10-bit input.
+         * <p>
+         * This feature implies that the codec is capable of encoding
+         * 10-bit format, and that it supports RGBA_1010102 as
+         * well as P010, and optionally RGBA_FP16 input formats.
+         * <p>
+         * The difference between this feature and {@link
+         * FEATURE_HdrEditing} is that HLG does not require the
+         * generation of HDR metadata and does not use an explicit HDR
+         * profile.
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_HLG_EDITING)
+        public static final String FEATURE_HlgEditing = "hlg-editing";
+
+        /**
+         * <b>video decoder only</b>: codec supports dynamically
+         * changing color aspects.
+         * <p>
+         * If true, the codec can propagate color aspect changes during
+         * decoding. This is only meaningful at session boundaries, e.g.
+         * upon processing Picture Parameter Sets prior to a new IDR.
+         * The color aspects may come from the bitstream, or may be
+         * provided using {@link MediaCodec#setParameters} calls.
+         * <p>
+         * If the codec supports both 8-bit and 10-bit profiles, this
+         * feature means that the codec can dynamically switch between 8
+         * and 10-bit profiles, but this is restricted to Surface mode
+         * only.
+         * <p>
+         * If the device supports HDR transfer functions, switching
+         * between SDR and HDR transfer is also supported. Together with
+         * the previous clause this means that switching between SDR and
+         * HDR sessions are supported in Surface mode, as SDR is
+         * typically encoded at 8-bit and HDR at 10-bit.
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_DYNAMIC_COLOR_ASPECTS)
+        public static final String FEATURE_DynamicColorAspects = "dynamic-color-aspects";
+
+        /**
          * Query codec feature capabilities.
          * <p>
          * These features are supported to be used by the codec.  These
@@ -712,29 +761,60 @@
             return checkFeature(name, mFlagsRequired);
         }
 
-        private static final Feature[] decoderFeatures = {
-            new Feature(FEATURE_AdaptivePlayback, (1 << 0), true),
-            new Feature(FEATURE_SecurePlayback,   (1 << 1), false),
-            new Feature(FEATURE_TunneledPlayback, (1 << 2), false),
-            new Feature(FEATURE_PartialFrame,     (1 << 3), false),
-            new Feature(FEATURE_FrameParsing,     (1 << 4), false),
-            new Feature(FEATURE_MultipleFrames,   (1 << 5), false),
-            new Feature(FEATURE_DynamicTimestamp, (1 << 6), false),
-            new Feature(FEATURE_LowLatency,       (1 << 7), true),
-            // feature to exclude codec from REGULAR codec list
-            new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true),
-        };
+        // Flags are used for feature list creation so separate this into a private
+        // static class to delay reading the flags only when constructing the list.
+        private static class FeatureList {
+            private static Feature[] getDecoderFeatures() {
+                ArrayList<Feature> features = new ArrayList();
+                features.add(new Feature(FEATURE_AdaptivePlayback, (1 << 0), true));
+                features.add(new Feature(FEATURE_SecurePlayback,   (1 << 1), false));
+                features.add(new Feature(FEATURE_TunneledPlayback, (1 << 2), false));
+                features.add(new Feature(FEATURE_PartialFrame,     (1 << 3), false));
+                features.add(new Feature(FEATURE_FrameParsing,     (1 << 4), false));
+                features.add(new Feature(FEATURE_MultipleFrames,   (1 << 5), false));
+                features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
+                features.add(new Feature(FEATURE_LowLatency,       (1 << 7), true));
+                if (android.media.codec.Flags.dynamicColorAspects()) {
+                    features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
+                }
 
-        private static final Feature[] encoderFeatures = {
-            new Feature(FEATURE_IntraRefresh, (1 << 0), false),
-            new Feature(FEATURE_MultipleFrames, (1 << 1), false),
-            new Feature(FEATURE_DynamicTimestamp, (1 << 2), false),
-            new Feature(FEATURE_QpBounds, (1 << 3), false),
-            new Feature(FEATURE_EncodingStatistics, (1 << 4), false),
-            new Feature(FEATURE_HdrEditing, (1 << 5), false),
-            // feature to exclude codec from REGULAR codec list
-            new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true),
-        };
+                // feature to exclude codec from REGULAR codec list
+                features.add(new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true));
+
+                return features.toArray(new Feature[0]);
+            };
+
+            private static Feature[] decoderFeatures = getDecoderFeatures();
+
+            private static Feature[] getEncoderFeatures() {
+                ArrayList<Feature> features = new ArrayList();
+
+                features.add(new Feature(FEATURE_IntraRefresh, (1 << 0), false));
+                features.add(new Feature(FEATURE_MultipleFrames, (1 << 1), false));
+                features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 2), false));
+                features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
+                features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
+                features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
+                if (android.media.codec.Flags.hlgEditing()) {
+                    features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
+                }
+
+                // feature to exclude codec from REGULAR codec list
+                features.add(new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true));
+
+                return features.toArray(new Feature[0]);
+            };
+
+            private static Feature[] encoderFeatures = getEncoderFeatures();
+
+            public static Feature[] getFeatures(boolean isEncoder) {
+                if (isEncoder) {
+                    return encoderFeatures;
+                } else {
+                    return decoderFeatures;
+                }
+            }
+        }
 
         /** @hide */
         public String[] validFeatures() {
@@ -749,10 +829,7 @@
         }
 
         private Feature[] getValidFeatures() {
-            if (!isEncoder()) {
-                return decoderFeatures;
-            }
-            return encoderFeatures;
+            return FeatureList.getFeatures(isEncoder());
         }
 
         private boolean checkFeature(String name, int flags) {
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 425db06..7fa3ed6 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -19,11 +19,14 @@
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
 import static com.android.media.flags.Flags.FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2;
+import static com.android.media.flags.Flags.FLAG_ENABLE_GET_TRANSFERABLE_ROUTES;
 import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2;
+import static com.android.media.flags.Flags.FLAG_ENABLE_SCREEN_OFF_SCANNING;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -42,9 +45,12 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -73,6 +79,48 @@
 //       Not only MediaRouter2, but also to service / manager / provider.
 // TODO: ensure thread-safe and document it
 public final class MediaRouter2 {
+
+    /**
+     * The state of a router not requesting route scanning.
+     *
+     * @hide
+     */
+    public static final int SCANNING_STATE_NOT_SCANNING = 0;
+
+    /**
+     * The state of a router requesting scanning only while the user interacts with its owner app.
+     *
+     * <p>The device's screen must be on and the app must be in the foreground to trigger scanning
+     * under this state.
+     *
+     * @hide
+     */
+    public static final int SCANNING_STATE_WHILE_INTERACTIVE = 1;
+
+    /**
+     * The state of a router requesting unrestricted scanning.
+     *
+     * <p>This state triggers scanning regardless of the restrictions required for {@link
+     * #SCANNING_STATE_WHILE_INTERACTIVE}.
+     *
+     * <p>Routers requesting unrestricted scanning must hold {@link
+     * Manifest.permission#MEDIA_ROUTING_CONTROL}.
+     *
+     * @hide
+     */
+    public static final int SCANNING_STATE_SCANNING_FULL = 2;
+
+    /** @hide */
+    @IntDef(
+            prefix = "SCANNING_STATE",
+            value = {
+                SCANNING_STATE_NOT_SCANNING,
+                SCANNING_STATE_WHILE_INTERACTIVE,
+                SCANNING_STATE_SCANNING_FULL
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScanningState {}
+
     private static final String TAG = "MR2";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final Object sSystemRouterLock = new Object();
@@ -123,6 +171,13 @@
     @GuardedBy("mLock")
     private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    private int mScreenOffScanRequestCount = 0;
+
+    @GuardedBy("mLock")
+    private int mScreenOnScanRequestCount = 0;
+
+    private final SparseArray<ScanRequest> mScanRequestsMap = new SparseArray<>();
     private final AtomicInteger mNextRequestId = new AtomicInteger(1);
     private final Handler mHandler;
 
@@ -335,6 +390,100 @@
         mImpl.stopScan();
     }
 
+    /**
+     * Requests the system to actively scan for routes based on the router's {@link
+     * RouteDiscoveryPreference route discovery preference}.
+     *
+     * <p>You must call {@link #cancelScanRequest(ScanToken)} promptly to preserve system resources
+     * like battery. Avoid scanning unless there is clear intention from the user to start routing
+     * their media.
+     *
+     * <p>{@code scanRequest} specifies relevant scanning options, like whether the system should
+     * scan with the screen off. Screen off scanning requires {@link
+     * Manifest.permission#MEDIA_ROUTING_CONTROL}
+     *
+     * <p>Proxy routers use the registered {@link RouteDiscoveryPreference} of their target routers.
+     *
+     * @return A unique {@link ScanToken} that identifies the scan request.
+     */
+    @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING)
+    @NonNull
+    public ScanToken requestScan(@NonNull ScanRequest scanRequest) {
+        Objects.requireNonNull(scanRequest, "scanRequest must not be null.");
+        ScanToken token = new ScanToken(mNextRequestId.getAndIncrement());
+
+        synchronized (mLock) {
+            boolean shouldUpdate =
+                    mScreenOffScanRequestCount == 0
+                            && (scanRequest.isScreenOffScan() || mScreenOnScanRequestCount == 0);
+
+            if (shouldUpdate) {
+                try {
+                    mImpl.updateScanningState(
+                            scanRequest.isScreenOffScan()
+                                    ? SCANNING_STATE_SCANNING_FULL
+                                    : SCANNING_STATE_WHILE_INTERACTIVE);
+
+                    if (scanRequest.isScreenOffScan()) {
+                        mScreenOffScanRequestCount++;
+                    } else {
+                        mScreenOnScanRequestCount++;
+                    }
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+            }
+
+            mScanRequestsMap.put(token.mId, scanRequest);
+            return token;
+        }
+    }
+
+    /**
+     * Releases the active scan request linked to the provided {@link ScanToken}.
+     *
+     * @see #requestScan(ScanRequest)
+     * @param token {@link ScanToken} of the {@link ScanRequest} to release.
+     * @throws IllegalArgumentException if the token does not match any active scan request.
+     */
+    @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING)
+    public void cancelScanRequest(@NonNull ScanToken token) {
+        Objects.requireNonNull(token, "token must not be null");
+
+        synchronized (mLock) {
+            ScanRequest request = mScanRequestsMap.get(token.mId);
+
+            if (request == null) {
+                throw new IllegalArgumentException(
+                        "The token does not match any active scan request");
+            }
+
+            boolean shouldUpdate =
+                    mScreenOffScanRequestCount == 1
+                            && (request.isScreenOffScan() || mScreenOnScanRequestCount == 1);
+
+            if (shouldUpdate) {
+                try {
+                    if (request.isScreenOffScan() && mScreenOnScanRequestCount == 0) {
+                        mImpl.updateScanningState(SCANNING_STATE_NOT_SCANNING);
+                    } else {
+                        mImpl.updateScanningState(SCANNING_STATE_WHILE_INTERACTIVE);
+                    }
+
+                    if (request.isScreenOffScan()) {
+                        mScreenOffScanRequestCount--;
+                    } else {
+                        mScreenOnScanRequestCount--;
+                    }
+                } catch (RemoteException ex) {
+                    ex.rethrowFromSystemServer();
+                }
+            }
+
+            mScanRequestsMap.remove(token.mId);
+        }
+    }
+
     private MediaRouter2(Context appContext) {
         mContext = appContext;
         mMediaRouterService =
@@ -1429,6 +1578,78 @@
     }
 
     /**
+     * Represents an active scan request registered in the system.
+     *
+     * <p>See {@link #requestScan(ScanRequest)} for more information.
+     */
+    @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING)
+    public static final class ScanToken {
+        private final int mId;
+
+        private ScanToken(int id) {
+            mId = id;
+        }
+    }
+
+    /**
+     * Represents a set of parameters for scanning requests.
+     *
+     * <p>See {@link #requestScan(ScanRequest)} for more details.
+     */
+    @FlaggedApi(FLAG_ENABLE_SCREEN_OFF_SCANNING)
+    public static final class ScanRequest {
+        private final boolean mIsScreenOffScan;
+
+        private ScanRequest(boolean isScreenOffScan) {
+            mIsScreenOffScan = isScreenOffScan;
+        }
+
+        /**
+         * Returns whether the scan request corresponds to a screen-off scan.
+         *
+         * @see #requestScan(ScanRequest)
+         */
+        public boolean isScreenOffScan() {
+            return mIsScreenOffScan;
+        }
+
+        /**
+         * Builder class for {@link ScanRequest}.
+         *
+         * @see #requestScan(ScanRequest)
+         */
+        public static final class Builder {
+            boolean mIsScreenOffScan;
+
+            /**
+             * Creates a builder for a {@link ScanRequest} instance.
+             *
+             * @see #requestScan(ScanRequest)
+             */
+            public Builder() {}
+
+            /**
+             * Sets whether the app is requesting to scan even while the screen is off, bypassing
+             * default scanning restrictions. Only companion apps holding {@link
+             * Manifest.permission#MEDIA_ROUTING_CONTROL} should set this to {@code true}.
+             *
+             * @see #requestScan(ScanRequest)
+             */
+            @NonNull
+            public Builder setScreenOffScan(boolean isScreenOffScan) {
+                mIsScreenOffScan = isScreenOffScan;
+                return this;
+            }
+
+            /** Returns a new {@link ScanRequest} instance. */
+            @NonNull
+            public ScanRequest build() {
+                return new ScanRequest(mIsScreenOffScan);
+            }
+        }
+    }
+
+    /**
      * A class to control media routing session in media route provider. For example,
      * selecting/deselecting/transferring to routes of a session can be done through this. Instances
      * are created when {@link TransferCallback#onTransfer(RoutingController, RoutingController)} is
@@ -1532,8 +1753,9 @@
         /**
          * Returns the unmodifiable list of transferable routes for the session.
          *
-         * @hide
+         * @see RoutingSessionInfo#getTransferableRoutes()
          */
+        @FlaggedApi(FLAG_ENABLE_GET_TRANSFERABLE_ROUTES)
         @NonNull
         public List<MediaRoute2Info> getTransferableRoutes() {
             List<String> transferableRoutes;
@@ -2092,6 +2314,9 @@
      * ProxyMediaRouter2Impl proxy} {@link MediaRouter2} instances.
      */
     private interface MediaRouter2Impl {
+
+        void updateScanningState(@ScanningState int scanningState) throws RemoteException;
+
         void startScan();
 
         void stopScan();
@@ -2195,11 +2420,17 @@
         }
 
         @Override
+        public void updateScanningState(int scanningState) throws RemoteException {
+            mMediaRouterService.updateScanningState(mClient, scanningState);
+        }
+
+        @Override
         public void startScan() {
             if (!mIsScanning.getAndSet(true)) {
                 if (mScanRequestCount.getAndIncrement() == 0) {
                     try {
-                        mMediaRouterService.startScan(mClient);
+                        mMediaRouterService.updateScanningState(
+                                mClient, SCANNING_STATE_WHILE_INTERACTIVE);
                     } catch (RemoteException ex) {
                         throw ex.rethrowFromSystemServer();
                     }
@@ -2221,7 +2452,8 @@
                                 })
                         == 0) {
                     try {
-                        mMediaRouterService.stopScan(mClient);
+                        mMediaRouterService.updateScanningState(
+                                mClient, SCANNING_STATE_NOT_SCANNING);
                     } catch (RemoteException ex) {
                         throw ex.rethrowFromSystemServer();
                     }
@@ -3041,6 +3273,18 @@
             // Do nothing.
         }
 
+        @Override
+        @GuardedBy("mLock")
+        public void updateScanningState(int scanningState) throws RemoteException {
+            if (scanningState != SCANNING_STATE_NOT_SCANNING) {
+                registerRouterStubIfNeededLocked();
+            }
+            mMediaRouterService.updateScanningStateWithRouter2(mStub, scanningState);
+            if (scanningState == SCANNING_STATE_NOT_SCANNING) {
+                unregisterRouterStubIfNeededLocked(/* isScanningStopping */ true);
+            }
+        }
+
         /**
          * Returns {@code null}. The client package name is only associated to proxy {@link
          * MediaRouter2} instances.
@@ -3103,7 +3347,7 @@
                                 mStub, mDiscoveryPreference);
                     }
 
-                    unregisterRouterStubIfNeededLocked();
+                    unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false);
 
                 } catch (RemoteException ex) {
                     Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex);
@@ -3313,7 +3557,7 @@
                 }
 
                 try {
-                    unregisterRouterStubIfNeededLocked();
+                    unregisterRouterStubIfNeededLocked(/* isScanningStopping */ false);
                 } catch (RemoteException ex) {
                     ex.rethrowFromSystemServer();
                 }
@@ -3331,10 +3575,12 @@
         }
 
         @GuardedBy("mLock")
-        private void unregisterRouterStubIfNeededLocked() throws RemoteException {
+        private void unregisterRouterStubIfNeededLocked(boolean isScanningStopping)
+                throws RemoteException {
             if (mStub != null
                     && mRouteCallbackRecords.isEmpty()
-                    && mNonSystemRoutingControllers.isEmpty()) {
+                    && mNonSystemRoutingControllers.isEmpty()
+                    && (mScanRequestsMap.size() == 0 || isScanningStopping)) {
                 mMediaRouterService.unregisterRouter2(mStub);
                 mStub = null;
             }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 06c0996..488d544 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -16,6 +16,9 @@
 
 package android.media;
 
+import static android.media.MediaRouter2.SCANNING_STATE_NOT_SCANNING;
+import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest;
@@ -174,7 +177,7 @@
     public void registerScanRequest() {
         if (mScanRequestCount.getAndIncrement() == 0) {
             try {
-                mMediaRouterService.startScan(mClient);
+                mMediaRouterService.updateScanningState(mClient, SCANNING_STATE_WHILE_INTERACTIVE);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -201,7 +204,7 @@
                 })
                 == 0) {
             try {
-                mMediaRouterService.stopScan(mClient);
+                mMediaRouterService.updateScanningState(mClient, SCANNING_STATE_NOT_SCANNING);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index df9ecdc..8dba040 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -85,8 +85,22 @@
 }
 
 flag {
+    name: "enable_get_transferable_routes"
+    namespace: "media_solutions"
+    description: "Exposes RoutingController#getTransferableRoutes() (previously hidden) to the public API."
+    bug: "323154573"
+}
+
+flag {
     name: "enable_prevention_of_keep_alive_route_providers"
     namespace: "media_solutions"
     description: "Enables mechanisms to prevent route providers from keeping malicious apps alive."
     bug: "263520343"
 }
+
+flag {
+    name: "enable_screen_off_scanning"
+    namespace: "media_solutions"
+    description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off."
+    bug: "281072508"
+}
diff --git a/opengl/java/android/opengl/OWNERS b/opengl/java/android/opengl/OWNERS
index 9c6c610..e340bc6 100644
--- a/opengl/java/android/opengl/OWNERS
+++ b/opengl/java/android/opengl/OWNERS
@@ -2,3 +2,5 @@
 
 [email protected]
 [email protected]
[email protected]
[email protected]
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index aed985e..f44b161 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -14,10 +14,10 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<resources>
     <style name="TextAppearance.PreferenceTitle.SettingsLib"
            parent="@android:style/TextAppearance.Material.Subhead">
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:textColor">@color/settingslib_text_color_primary</item>
         <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
         <item name="android:textSize">20sp</item>
     </style>
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt
deleted file mode 100644
index fd253c6..0000000
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/view/accessibility/data/repository/FakeCaptioningRepository.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package com.android.settingslib.view.accessibility.data.repository
-
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-class FakeCaptioningRepository : CaptioningRepository {
-
-    private val mutableIsSystemAudioCaptioningEnabled = MutableStateFlow(false)
-    override val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
-        get() = mutableIsSystemAudioCaptioningEnabled.asStateFlow()
-
-    private val mutableIsSystemAudioCaptioningUiEnabled = MutableStateFlow(false)
-    override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
-        get() = mutableIsSystemAudioCaptioningUiEnabled.asStateFlow()
-
-    override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
-        mutableIsSystemAudioCaptioningEnabled.value = isEnabled
-    }
-
-    fun setIsSystemAudioCaptioningUiEnabled(isSystemAudioCaptioningUiEnabled: Boolean) {
-        mutableIsSystemAudioCaptioningUiEnabled.value = isSystemAudioCaptioningUiEnabled
-    }
-}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index cdcf8e4..8ae117e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -264,5 +264,6 @@
         Settings.Secure.EVEN_DIMMER_ACTIVATED,
         Settings.Secure.EVEN_DIMMER_MIN_NITS,
         Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
+        Settings.Secure.CAMERA_EXTENSIONS_FALLBACK
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 35d45a9..5adae375 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -418,5 +418,6 @@
         VALIDATORS.put(Secure.CREDENTIAL_SERVICE_PRIMARY, NULLABLE_COMPONENT_NAME_VALIDATOR);
         VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR);
         VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.CAMERA_EXTENSIONS_FALLBACK, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 84ef6e5..926e181 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -917,6 +917,9 @@
     <!-- Permissions required for CTS test - GrammaticalInflectionManagerTest -->
     <uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
 
+    <!-- Permission required for CTS test - CtsPackageManagerTestCases-->
+    <uses-permission android:name="android.permission.DOMAIN_VERIFICATION_AGENT" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 6a4f92c..7a4e60a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -416,3 +416,14 @@
    bug: "282007590"
 }
 
+flag {
+    name: "notification_row_user_context"
+    namespace: "systemui"
+    description: "Create a user-specific Context for the ImageResolver in ExpandableNotificationRow"
+        " (based on the NotificationEntry's user)."
+    bug: "317503801"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
new file mode 100644
index 0000000..abe1e3d
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.surfaceeffects.loadingeffect
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Paint
+import android.graphics.RenderEffect
+import android.view.View
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
+
+/**
+ * Plays loading effect with the given configuration.
+ *
+ * @param baseType immutable base shader type. This is used for constructing the shader. Reconstruct
+ *   the [LoadingEffect] if the base type needs to be changed.
+ * @param config immutable parameters that are used for drawing the effect.
+ * @param paintCallback triggered every frame when animation is playing. Use this to draw the effect
+ *   with [Canvas.drawPaint].
+ * @param renderEffectCallback triggered every frame when animation is playing. Use this to draw the
+ *   effect with [RenderEffect].
+ * @param animationStateChangedCallback triggered when the [AnimationState] changes. Optional.
+ *
+ * The client is responsible to actually draw the [Paint] or [RenderEffect] returned in the
+ * callback. Note that [View.invalidate] must be called on each callback. There are a few ways to
+ * render the effect:
+ * 1) Use [Canvas.drawPaint]. (Preferred. Significantly cheaper!)
+ * 2) Set [RenderEffect] to the [View]. (Good for chaining effects.)
+ * 3) Use [RenderNode.setRenderEffect]. (This may be least preferred, as 2 should do what you want.)
+ *
+ * <p>First approach is more performant than other ones because [RenderEffect] forces an
+ * intermediate render pass of the View to a texture to feed into it.
+ *
+ * <p>If going with the first approach, your custom [View] would look like as follow:
+ * <pre>{@code
+ *     private var paint: Paint? = null
+ *     // Override [View.onDraw].
+ *     override fun onDraw(canvas: Canvas) {
+ *         // RuntimeShader requires hardwareAcceleration.
+ *         if (!canvas.isHardwareAccelerated) return
+ *
+ *         paint?.let { canvas.drawPaint(it) }
+ *     }
+ *
+ *     // This is called [Callback.onDraw]
+ *     fun draw(paint: Paint) {
+ *         this.paint = paint
+ *
+ *         // Must call invalidate to trigger View#onDraw
+ *         invalidate()
+ *     }
+ * }</pre>
+ *
+ * <p>If going with the second approach, it doesn't require an extra custom [View], and it is as
+ * simple as calling [View.setRenderEffect] followed by [View.invalidate]. You can also chain the
+ * effect with other [RenderEffect].
+ *
+ * <p>Third approach is an option, but it's more of a boilerplate so you would like to stick with
+ * the second option. If you want to go with this option for some reason, below is the example:
+ * <pre>{@code
+ *     // Initialize the shader and paint to use to pass into the [Canvas].
+ *     private val renderNode = RenderNode("LoadingEffect")
+ *
+ *     // Override [View.onDraw].
+ *     override fun onDraw(canvas: Canvas) {
+ *         // RuntimeShader requires hardwareAcceleration.
+ *         if (!canvas.isHardwareAccelerated) return
+ *
+ *         if (renderNode.hasDisplayList()) {
+ *             canvas.drawRenderNode(renderNode)
+ *         }
+ *     }
+ *
+ *     // This is called [Callback.onDraw]
+ *     fun draw(renderEffect: RenderEffect) {
+ *         renderNode.setPosition(0, 0, width, height)
+ *         renderNode.setRenderEffect(renderEffect)
+ *
+ *         val recordingCanvas = renderNode.beginRecording()
+ *         // We need at least 1 drawing instruction.
+ *         recordingCanvas.drawColor(Color.TRANSPARENT)
+ *         renderNode.endRecording()
+ *
+ *         // Must call invalidate to trigger View#onDraw
+ *         invalidate()
+ *     }
+ * }</pre>
+ */
+class LoadingEffect
+private constructor(
+    baseType: TurbulenceNoiseShader.Companion.Type,
+    private val config: TurbulenceNoiseAnimationConfig,
+    private val paintCallback: PaintDrawCallback?,
+    private val renderEffectCallback: RenderEffectDrawCallback?,
+    private val animationStateChangedCallback: AnimationStateChangedCallback? = null
+) {
+    constructor(
+        baseType: TurbulenceNoiseShader.Companion.Type,
+        config: TurbulenceNoiseAnimationConfig,
+        paintCallback: PaintDrawCallback,
+        animationStateChangedCallback: AnimationStateChangedCallback? = null
+    ) : this(
+        baseType,
+        config,
+        paintCallback,
+        renderEffectCallback = null,
+        animationStateChangedCallback
+    )
+    constructor(
+        baseType: TurbulenceNoiseShader.Companion.Type,
+        config: TurbulenceNoiseAnimationConfig,
+        renderEffectCallback: RenderEffectDrawCallback,
+        animationStateChangedCallback: AnimationStateChangedCallback? = null
+    ) : this(
+        baseType,
+        config,
+        paintCallback = null,
+        renderEffectCallback,
+        animationStateChangedCallback
+    )
+
+    private val turbulenceNoiseShader: TurbulenceNoiseShader =
+        TurbulenceNoiseShader(baseType).apply { applyConfig(config) }
+    private var currentAnimator: ValueAnimator? = null
+    private var state: AnimationState = AnimationState.NOT_PLAYING
+        set(value) {
+            if (field != value) {
+                animationStateChangedCallback?.onStateChanged(field, value)
+                field = value
+            }
+        }
+
+    // We create a paint instance only if the client renders it with Paint.
+    private val paint =
+        if (paintCallback != null) {
+            Paint().apply { this.shader = turbulenceNoiseShader }
+        } else {
+            null
+        }
+
+    /** Plays LoadingEffect. */
+    fun play() {
+        if (state != AnimationState.NOT_PLAYING) {
+            return // Ignore if any of the animation is playing.
+        }
+
+        playEaseIn()
+    }
+
+    // TODO(b/237282226): Support force finish.
+    /** Finishes the main animation, which triggers the ease-out animation. */
+    fun finish() {
+        if (state == AnimationState.MAIN) {
+            // Calling Animator#end sets the animation state back to the initial state. Using pause
+            // to avoid visual artifacts.
+            currentAnimator?.pause()
+            currentAnimator = null
+
+            playEaseOut()
+        }
+    }
+
+    /** Updates the noise color dynamically. */
+    fun updateColor(newColor: Int) {
+        turbulenceNoiseShader.setColor(newColor)
+    }
+
+    /**
+     * Retrieves the noise offset x, y, z values. This is useful for replaying the animation
+     * smoothly from the last animation, by passing in the last values to the next animation.
+     */
+    fun getNoiseOffset(): Array<Float> {
+        return arrayOf(
+            turbulenceNoiseShader.noiseOffsetX,
+            turbulenceNoiseShader.noiseOffsetY,
+            turbulenceNoiseShader.noiseOffsetZ
+        )
+    }
+
+    private fun playEaseIn() {
+        if (state != AnimationState.NOT_PLAYING) {
+            return
+        }
+        state = AnimationState.EASE_IN
+
+        val animator = ValueAnimator.ofFloat(0f, 1f)
+        animator.duration = config.easeInDuration.toLong()
+
+        // Animation should start from the initial position to avoid abrupt transition.
+        val initialX = turbulenceNoiseShader.noiseOffsetX
+        val initialY = turbulenceNoiseShader.noiseOffsetY
+        val initialZ = turbulenceNoiseShader.noiseOffsetZ
+
+        animator.addUpdateListener { updateListener ->
+            val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
+            val progress = updateListener.animatedValue as Float
+
+            turbulenceNoiseShader.setNoiseMove(
+                initialX + timeInSec * config.noiseMoveSpeedX,
+                initialY + timeInSec * config.noiseMoveSpeedY,
+                initialZ + timeInSec * config.noiseMoveSpeedZ
+            )
+
+            // TODO: Replace it with a better curve.
+            turbulenceNoiseShader.setOpacity(progress * config.luminosityMultiplier)
+
+            draw()
+        }
+
+        animator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    currentAnimator = null
+                    playMain()
+                }
+            }
+        )
+
+        animator.start()
+        this.currentAnimator = animator
+    }
+
+    private fun playMain() {
+        if (state != AnimationState.EASE_IN) {
+            return
+        }
+        state = AnimationState.MAIN
+
+        val animator = ValueAnimator.ofFloat(0f, 1f)
+        animator.duration = config.maxDuration.toLong()
+
+        // Animation should start from the initial position to avoid abrupt transition.
+        val initialX = turbulenceNoiseShader.noiseOffsetX
+        val initialY = turbulenceNoiseShader.noiseOffsetY
+        val initialZ = turbulenceNoiseShader.noiseOffsetZ
+
+        turbulenceNoiseShader.setOpacity(config.luminosityMultiplier)
+
+        animator.addUpdateListener { updateListener ->
+            val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
+            turbulenceNoiseShader.setNoiseMove(
+                initialX + timeInSec * config.noiseMoveSpeedX,
+                initialY + timeInSec * config.noiseMoveSpeedY,
+                initialZ + timeInSec * config.noiseMoveSpeedZ
+            )
+
+            draw()
+        }
+
+        animator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    currentAnimator = null
+                    playEaseOut()
+                }
+            }
+        )
+
+        animator.start()
+        this.currentAnimator = animator
+    }
+
+    private fun playEaseOut() {
+        if (state != AnimationState.MAIN) {
+            return
+        }
+        state = AnimationState.EASE_OUT
+
+        val animator = ValueAnimator.ofFloat(0f, 1f)
+        animator.duration = config.easeOutDuration.toLong()
+
+        // Animation should start from the initial position to avoid abrupt transition.
+        val initialX = turbulenceNoiseShader.noiseOffsetX
+        val initialY = turbulenceNoiseShader.noiseOffsetY
+        val initialZ = turbulenceNoiseShader.noiseOffsetZ
+
+        animator.addUpdateListener { updateListener ->
+            val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
+            val progress = updateListener.animatedValue as Float
+
+            turbulenceNoiseShader.setNoiseMove(
+                initialX + timeInSec * config.noiseMoveSpeedX,
+                initialY + timeInSec * config.noiseMoveSpeedY,
+                initialZ + timeInSec * config.noiseMoveSpeedZ
+            )
+
+            // TODO: Replace it with a better curve.
+            turbulenceNoiseShader.setOpacity((1f - progress) * config.luminosityMultiplier)
+
+            draw()
+        }
+
+        animator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    currentAnimator = null
+                    state = AnimationState.NOT_PLAYING
+                }
+            }
+        )
+
+        animator.start()
+        this.currentAnimator = animator
+    }
+
+    private fun draw() {
+        paintCallback?.onDraw(paint!!)
+        renderEffectCallback?.onDraw(
+            RenderEffect.createRuntimeShaderEffect(turbulenceNoiseShader, "in_src")
+        )
+    }
+
+    companion object {
+        /**
+         * States of the loading effect animation.
+         *
+         * <p>The state is designed to be follow the order below: [AnimationState.EASE_IN],
+         * [AnimationState.MAIN], [AnimationState.EASE_OUT]. Note that ease in and out don't
+         * necessarily mean the acceleration and deceleration in the animation curve. They simply
+         * mean each stage of the animation. (i.e. Intro, core, and rest)
+         */
+        enum class AnimationState {
+            EASE_IN,
+            MAIN,
+            EASE_OUT,
+            NOT_PLAYING
+        }
+
+        /** Client must implement one of the draw callbacks. */
+        interface PaintDrawCallback {
+            /**
+             * A callback with a [Paint] object that contains shader info, which is triggered every
+             * frame while animation is playing. Note that the [Paint] object here is always the
+             * same instance.
+             */
+            fun onDraw(loadingPaint: Paint)
+        }
+
+        interface RenderEffectDrawCallback {
+            /**
+             * A callback with a [RenderEffect] object that contains shader info, which is triggered
+             * every frame while animation is playing. Note that the [RenderEffect] instance is
+             * different each time to update shader uniforms.
+             */
+            fun onDraw(loadingRenderEffect: RenderEffect)
+        }
+
+        /** Optional callback that is triggered when the animation state changes. */
+        interface AnimationStateChangedCallback {
+            /**
+             * A callback that's triggered when the [AnimationState] changes. Example usage is
+             * performing a cleanup when [AnimationState] becomes [NOT_PLAYING].
+             */
+            fun onStateChanged(oldState: AnimationState, newState: AnimationState) {}
+        }
+
+        private const val MS_TO_SEC = 0.001f
+
+        private val TAG = LoadingEffect::class.java.simpleName
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index 30108ac..8dd90a8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -30,6 +30,7 @@
     companion object {
         private const val UNIFORMS =
             """
+            uniform shader in_src; // Needed to support RenderEffect.
             uniform float in_gridNum;
             uniform vec3 in_noiseMove;
             uniform vec2 in_size;
@@ -114,6 +115,7 @@
         setSize(config.width, config.height)
         setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
         setInverseNoiseLuminosity(config.shouldInverseNoiseLuminosity)
+        setNoiseMove(config.noiseOffsetX, config.noiseOffsetY, config.noiseOffsetZ)
     }
 
     /** Sets the number of grid for generating noise. */
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 9a34d6f..36ab46b4 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
 import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
@@ -104,7 +105,7 @@
         throwComposeUnavailableError()
     }
 
-    override fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View {
+    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
         throwComposeUnavailableError()
     }
 
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt
new file mode 100644
index 0000000..aeb5c5d
--- /dev/null
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.captioning
+
+import dagger.Module
+
+@Module interface CaptioningModule
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 51d2a03..5b6aa09 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.communal.ui.compose.CommunalContainer
 import com.android.systemui.communal.ui.compose.CommunalHub
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
@@ -161,7 +162,7 @@
         }
     }
 
-    override fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View {
+    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
         return ComposeView(context).apply {
             setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 92bc1f1..bc85513 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -23,7 +23,9 @@
 import com.android.compose.animation.scene.updateSceneTransitionLayoutState
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.ui.compose.extensions.allowGestures
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.transform
 
@@ -51,7 +53,7 @@
 @Composable
 fun CommunalContainer(
     modifier: Modifier = Modifier,
-    viewModel: BaseCommunalViewModel,
+    viewModel: CommunalViewModel,
 ) {
     val currentScene: SceneKey by
         viewModel.currentScene
@@ -63,6 +65,7 @@
             onChangeScene = { viewModel.onSceneChanged(it.toCommunalSceneKey()) },
             transitions = sceneTransitions,
         )
+    val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
 
     // This effect exposes the SceneTransitionLayout's observable transition state to the rest of
     // the system, and unsets it when the view is disposed to avoid a memory leak.
@@ -75,7 +78,7 @@
 
     SceneTransitionLayout(
         state = sceneTransitionLayoutState,
-        modifier = modifier.fillMaxSize(),
+        modifier = modifier.fillMaxSize().allowGestures(allowed = touchesAllowed),
         swipeSourceDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
     ) {
         scene(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index 22aa837..881947e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -161,9 +161,9 @@
     private var isOnRemoveButton = false
 
     fun onStarted() {
-        // assume item will be added to the end.
-        contentListState.list.add(placeHolder)
+        // assume item will be added to the second to last position before CTA tile.
         placeHolderIndex = contentListState.list.size - 1
+        placeHolderIndex?.let { contentListState.list.add(it, placeHolder) }
     }
 
     fun onMoved(event: DragAndDropEvent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 1cbc992..46554a4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -29,10 +29,13 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBars
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.rememberScrollState
@@ -125,6 +128,9 @@
             remember(lifecycleOwner, viewModel) {
                 viewModel.getFooterActionsViewModel(lifecycleOwner)
             }
+
+        // ############## SCROLLING ################
+
         val scrollState = rememberScrollState()
         // When animating into the scene, we don't want it to be able to scroll, as it could mess
         // up with the expansion animation.
@@ -142,6 +148,18 @@
             }
         }
 
+        // ############# NAV BAR paddings ###############
+
+        val navBarBottomHeight =
+            WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
+        val density = LocalDensity.current
+
+        LaunchedEffect(navBarBottomHeight, density) {
+            with(density) {
+                viewModel.qsSceneAdapter.applyBottomNavBarPadding(navBarBottomHeight.roundToPx())
+            }
+        }
+
         // This is the background for the whole scene, as the elements don't necessarily provide
         // a background that extends to the edges.
         Spacer(
@@ -154,8 +172,13 @@
             horizontalAlignment = Alignment.CenterHorizontally,
             modifier =
                 Modifier.fillMaxSize()
-                    // bottom should be tied to insets
-                    .padding(bottom = 16.dp)
+                    .then(
+                        if (isCustomizing) {
+                            Modifier.padding(top = 48.dp)
+                        } else {
+                            Modifier.padding(bottom = navBarBottomHeight)
+                        }
+                    )
         ) {
             Box(modifier = Modifier.fillMaxSize().weight(1f)) {
                 val shadeHeaderAndQuickSettingsModifier =
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
new file mode 100644
index 0000000..228111d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.button.ui.composable
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedIconToggleButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import kotlinx.coroutines.flow.StateFlow
+
+class ToggleButtonComponent(
+    private val viewModelFlow: StateFlow<ToggleButtonViewModel?>,
+    private val onCheckedChange: (isChecked: Boolean) -> Unit
+) : ComposeVolumePanelUiComponent {
+
+    @Composable
+    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+        val viewModelByState by viewModelFlow.collectAsState()
+        val viewModel = viewModelByState ?: return
+        Column(
+            modifier = modifier,
+            verticalArrangement = Arrangement.spacedBy(12.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            OutlinedIconToggleButton(
+                modifier = Modifier.height(64.dp).fillMaxWidth(),
+                checked = viewModel.isChecked,
+                onCheckedChange = onCheckedChange,
+                colors =
+                    IconButtonDefaults.outlinedIconToggleButtonColors(
+                        containerColor = MaterialTheme.colorScheme.surface,
+                        contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+                        checkedContainerColor = MaterialTheme.colorScheme.primaryContainer,
+                        checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+                    ),
+                border = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
+            ) {
+                Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+            }
+            Text(
+                text = viewModel.label.toString(),
+                style = MaterialTheme.typography.labelMedium,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt
new file mode 100644
index 0000000..7d431d9
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/captioning/CaptioningModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.captioning
+
+import com.android.systemui.volume.panel.component.button.ui.composable.ToggleButtonComponent
+import com.android.systemui.volume.panel.component.captioning.domain.CaptioningAvailabilityCriteria
+import com.android.systemui.volume.panel.component.captioning.ui.viewmodel.CaptioningViewModel
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+/** Dagger module, that provides Captioning Volume Panel UI functionality. */
+@Module
+interface CaptioningModule {
+
+    @Binds
+    @IntoMap
+    @StringKey(VolumePanelComponents.CAPTIONING)
+    fun bindComponentAvailabilityCriteria(
+        criteria: CaptioningAvailabilityCriteria
+    ): ComponentAvailabilityCriteria
+
+    companion object {
+
+        @Provides
+        @IntoMap
+        @StringKey(VolumePanelComponents.CAPTIONING)
+        fun provideVolumePanelUiComponent(viewModel: CaptioningViewModel): VolumePanelUiComponent =
+            ToggleButtonComponent(
+                viewModel.buttonViewModel,
+                viewModel::setIsSystemAudioCaptioningEnabled,
+            )
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index a7ec93f..e8d5966 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -19,26 +19,39 @@
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.Slider
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
-import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
 
 @Composable
 fun VolumePanelComposeScope.VerticalVolumePanelContent(
-    components: List<ComponentState>,
+    layout: ComponentsLayout,
     modifier: Modifier = Modifier,
 ) {
     Column(
         modifier = modifier,
         verticalArrangement = Arrangement.spacedBy(20.dp),
     ) {
-        Slider(0.5f, {})
-        for (component in components) {
+        for (component in layout.contentComponents) {
             AnimatedVisibility(component.isVisible) {
                 with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
             }
         }
+        if (layout.footerComponents.isNotEmpty()) {
+            Row(
+                modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+                horizontalArrangement = Arrangement.spacedBy(20.dp)
+            ) {
+                for (component in layout.footerComponents) {
+                    with(component.component as ComposeVolumePanelUiComponent) {
+                        Content(Modifier.weight(1f))
+                    }
+                }
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index 3487184..60d03fc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -21,14 +21,15 @@
 import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.animation.slideInVertically
 import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.navigationBarsPadding
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
@@ -37,7 +38,9 @@
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.res.dimensionResource
 import com.android.compose.theme.PlatformTheme
 import com.android.systemui.res.R
@@ -64,14 +67,17 @@
             }
         }
 
-        Column(
-            modifier =
-                modifier
-                    .fillMaxSize()
-                    .statusBarsPadding()
-                    .clickable(onClick = { viewModel.dismissPanel() }),
-            verticalArrangement = Arrangement.Bottom,
+        Box(
+            modifier = modifier.fillMaxSize(),
+            contentAlignment = Alignment.BottomCenter,
         ) {
+            Spacer(
+                modifier =
+                    Modifier.fillMaxSize()
+                        .alpha(0.32f)
+                        .background(MaterialTheme.colorScheme.scrim)
+                        .clickable(onClick = { viewModel.dismissPanel() })
+            )
             AnimatedVisibility(
                 visibleState = transitionState,
                 enter = slideInVertically { it },
@@ -80,7 +86,7 @@
                 val radius = dimensionResource(R.dimen.volume_panel_corner_radius)
                 Surface(
                     shape = RoundedCornerShape(topStart = radius, topEnd = radius),
-                    color = MaterialTheme.colorScheme.surfaceBright,
+                    color = MaterialTheme.colorScheme.surfaceContainer,
                 ) {
                     Column {
                         components?.let { componentsState ->
@@ -97,7 +103,7 @@
 private fun VolumePanelComposeScope.Components(state: ComponentsLayout) {
     if (orientation == Configuration.ORIENTATION_PORTRAIT) {
         VerticalVolumePanelContent(
-            components = state.contentComponents,
+            state,
             modifier = Modifier.padding(dimensionResource(R.dimen.volume_panel_content_padding)),
         )
     } else {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index f70b6a5..b299ca7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
 import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
 import com.android.systemui.testKosmos
@@ -102,6 +103,7 @@
                 testScope,
                 kosmos.communalInteractor,
                 kosmos.communalTutorialInteractor,
+                kosmos.shadeInteractor,
                 mediaHost,
                 logcatLogBuffer("CommunalViewModelTest"),
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index cae20d0..f8573cc2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -414,4 +414,56 @@
             verify(qsImpl!!).onConfigurationChanged(configuration)
             verify(qsImpl!!.view).dispatchConfigurationChanged(configuration)
         }
+
+    @Test
+    fun dispatchNavBarSize_beforeInflation() =
+        testScope.runTest {
+            runCurrent()
+            val navBarHeight = 171
+
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.applyBottomNavBarPadding(navBarHeight)
+            underTest.inflate(context)
+            runCurrent()
+
+            verify(qsImpl!!).applyBottomNavBarToCustomizerPadding(navBarHeight)
+        }
+
+    @Test
+    fun dispatchNavBarSize_afterInflation() =
+        testScope.runTest {
+            runCurrent()
+            val navBarHeight = 171
+
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+
+            underTest.applyBottomNavBarPadding(navBarHeight)
+            runCurrent()
+
+            verify(qsImpl!!).applyBottomNavBarToCustomizerPadding(navBarHeight)
+        }
+
+    @Test
+    fun dispatchNavBarSize_reinflation() =
+        testScope.runTest {
+            runCurrent()
+            val navBarHeight = 171
+
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+
+            underTest.applyBottomNavBarPadding(navBarHeight)
+            runCurrent()
+
+            underTest.inflate(context)
+            runCurrent()
+
+            verify(qsImpl!!).applyBottomNavBarToCustomizerPadding(navBarHeight)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
new file mode 100644
index 0000000..610195f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.captioning.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.view.accessibility.data.repository.captioningInteractor
+import com.android.systemui.view.accessibility.data.repository.captioningRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CaptioningViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: CaptioningViewModel
+
+    @Before
+    fun setup() {
+        underTest =
+            with(kosmos) {
+                CaptioningViewModel(context, captioningInteractor, testScope.backgroundScope)
+            }
+    }
+
+    @Test
+    fun captioningDisabled_buttonViewModel_notChecked() {
+        with(kosmos) {
+            testScope.runTest {
+                captioningRepository.setIsSystemAudioCaptioningEnabled(false)
+
+                val buttonViewModel by collectLastValue(underTest.buttonViewModel)
+                runCurrent()
+
+                assertThat(buttonViewModel!!.isChecked).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun captioningDisabled_buttonViewModel_checked() {
+        with(kosmos) {
+            testScope.runTest {
+                captioningRepository.setIsSystemAudioCaptioningEnabled(true)
+
+                val buttonViewModel by collectLastValue(underTest.buttonViewModel)
+                runCurrent()
+
+                assertThat(buttonViewModel!!.isChecked).isTrue()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index 35d9698..7c99360 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -33,43 +33,37 @@
 class DefaultComponentsLayoutManagerTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
-    private val underTest: ComponentsLayoutManager = DefaultComponentsLayoutManager()
+    private val underTest: ComponentsLayoutManager =
+        DefaultComponentsLayoutManager(
+            BOTTOM_BAR,
+            headerComponents = listOf(COMPONENT_1),
+            footerComponents = listOf(COMPONENT_2),
+        )
 
     @Test
-    fun bottomBar_isSet() {
+    fun correspondingComponents_areSet() {
         val bottomBarComponentState =
             ComponentState(BOTTOM_BAR, kosmos.mockVolumePanelUiComponent, false)
+        val component1 = ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false)
+        val component2 = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
+        val component3 = ComponentState(COMPONENT_3, kosmos.mockVolumePanelUiComponent, false)
+        val component4 = ComponentState(COMPONENT_4, kosmos.mockVolumePanelUiComponent, false)
         val layout =
             underTest.layout(
                 VolumePanelState(0, false),
-                setOf(
-                    bottomBarComponentState,
-                    ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false),
-                    ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false),
-                )
+                setOf(bottomBarComponentState, component1, component2, component3, component4)
             )
 
         Truth.assertThat(layout.bottomBarComponent).isEqualTo(bottomBarComponentState)
-    }
-
-    @Test
-    fun componentsAreInOrder() {
-        val bottomBarComponentState =
-            ComponentState(BOTTOM_BAR, kosmos.mockVolumePanelUiComponent, false)
-        val component1State = ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false)
-        val component2State = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
-        val layout =
-            underTest.layout(
-                VolumePanelState(0, false),
-                setOf(
-                    bottomBarComponentState,
-                    component1State,
-                    component2State,
-                )
-            )
-
-        Truth.assertThat(layout.contentComponents[0]).isEqualTo(component1State)
-        Truth.assertThat(layout.contentComponents[1]).isEqualTo(component2State)
+        Truth.assertThat(layout.headerComponents)
+            .containsExactlyElementsIn(listOf(component1))
+            .inOrder()
+        Truth.assertThat(layout.footerComponents)
+            .containsExactlyElementsIn(listOf(component2))
+            .inOrder()
+        Truth.assertThat(layout.contentComponents)
+            .containsExactlyElementsIn(listOf(component3, component4))
+            .inOrder()
     }
 
     @Test(expected = IllegalStateException::class)
@@ -89,5 +83,7 @@
         const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
         const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
         const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+        const val COMPONENT_3: VolumePanelComponentKey = "test_component:3"
+        const val COMPONENT_4: VolumePanelComponentKey = "test_component:4"
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index c4c9cc6..910f71e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
 import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
-import com.android.systemui.volume.panel.ui.layout.FakeComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
 import com.android.systemui.volume.panel.unavailableCriteria
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +45,7 @@
 class VolumePanelViewModelTest : SysuiTestCase() {
 
     private val kosmos =
-        testKosmos().apply {
-            componentsLayoutManager = FakeComponentsLayoutManager { it.key == BOTTOM_BAR }
-        }
+        testKosmos().apply { componentsLayoutManager = DefaultComponentsLayoutManager(BOTTOM_BAR) }
 
     private val testableResources = context.orCreateTestableResources
 
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index e853f02..4e540de 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -169,9 +169,6 @@
     <dimen name="weather_clock_smartspace_translateX">0dp</dimen>
     <dimen name="weather_clock_smartspace_translateY">0dp</dimen>
 
-    <!-- Additional length to add to the SFPS sensor length we get from framework so that the length
-     of the progress bar matches the length of the power button  -->
-    <dimen name="sfps_progress_bar_length_extra_padding">12dp</dimen>
     <!-- Thickness of the progress bar we show for the SFPS based authentication. -->
     <dimen name="sfps_progress_bar_thickness">6dp</dimen>
     <!-- Padding from the edge of the screen for the progress bar -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b30e4a2..64c6cfa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1437,6 +1437,8 @@
     <!-- Label for button to go to sound settings screen [CHAR_LIMIT=30] -->
     <string name="volume_panel_dialog_settings_button">Settings</string>
 
+    <string name="volume_panel_captioning_title">Live Caption</string>
+
     <!-- Title for notification after audio lowers -->
     <string name="csd_lowered_title" product="default">Volume lowered to safer level</string>
     <!-- Message shown in notification after system lowers audio -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
index 5e71bbe..bed225b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
@@ -26,7 +26,7 @@
 import android.view.SurfaceControlViewHost;
 import android.view.View;
 import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
+import android.window.InputTransferToken;
 
 /**
  * A generic receiver that specifically handles SurfaceView request created by {@link
@@ -69,13 +69,10 @@
 
             IBinder hostToken = SurfaceViewRequestUtils.getHostToken(bundle);
 
-            WindowlessWindowManager windowlessWindowManager =
-                    new WindowlessWindowManager(context.getResources().getConfiguration(),
-                            surfaceControl, hostToken);
             DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
             mSurfaceControlViewHost = new SurfaceControlViewHost(context,
                     dm.getDisplay(SurfaceViewRequestUtils.getDisplayId(bundle)),
-                    windowlessWindowManager, "SurfaceViewRequestReceiver");
+                    new InputTransferToken(hostToken), "SurfaceViewRequestReceiver");
             WindowManager.LayoutParams layoutParams =
                     new WindowManager.LayoutParams(
                             viewSize.getWidth(),
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 5171a1f..d2fda4c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -29,7 +29,6 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.os.Binder;
 import android.os.Handler;
 import android.util.SparseArray;
 import android.view.Display;
@@ -39,6 +38,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+import android.window.InputTransferToken;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -114,7 +114,7 @@
 
             Supplier<SurfaceControlViewHost> scvhSupplier = () ->
                     Flags.createWindowlessWindowMagnifier() ? new SurfaceControlViewHost(mContext,
-                            mContext.getDisplay(), new Binder(), TAG) : null;
+                            mContext.getDisplay(), new InputTransferToken(), TAG) : null;
 
             return new WindowMagnificationController(
                     windowContext,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 40d2d16..febfd4c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -28,6 +28,8 @@
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.controls.ui.MediaHostState
 import com.android.systemui.media.dagger.MediaModule
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import javax.inject.Inject
 import javax.inject.Named
 import kotlinx.coroutines.CoroutineScope
@@ -51,6 +53,7 @@
     @Application private val scope: CoroutineScope,
     private val communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
+    shadeInteractor: ShadeInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     @CommunalLog logBuffer: LogBuffer,
 ) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -81,6 +84,9 @@
     override val isPopupOnDismissCtaShowing: Flow<Boolean> =
         _isPopupOnDismissCtaShowing.asStateFlow()
 
+    /** Whether touches should be disabled in communal */
+    val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
+
     init {
         // Initialize our media host for the UMO. This only needs to happen once and must be done
         // before the MediaHierarchyManager attempts to move the UMO to the hub.
@@ -114,6 +120,7 @@
     }
 
     private var delayedHidePopupJob: Job? = null
+
     private fun schedulePopupHiding() {
         cancelDelayedPopupHiding()
         delayedHidePopupJob =
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 9a4dfdd..4e23ecd9 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
 import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
@@ -116,7 +117,7 @@
     ): View
 
     /** Creates a container that hosts the communal UI and handles gesture transitions. */
-    fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View
+    fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View
 
     /** Creates a [View] that represents the Lockscreen. */
     fun createLockscreen(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index c14917b..f95efaa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -40,8 +40,13 @@
 import android.view.WindowManager
 import android.widget.FrameLayout
 import android.widget.TextView
+import android.window.InputTransferToken
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
 import androidx.core.view.isInvisible
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
@@ -206,7 +211,7 @@
                 SurfaceControlViewHost(
                     context,
                     displayManager.getDisplay(DEFAULT_DISPLAY),
-                    hostToken,
+                    if (hostToken == null) null else InputTransferToken(hostToken),
                     "KeyguardPreviewRenderer"
                 )
             disposables.add(DisposableHandle { host.release() })
@@ -392,7 +397,7 @@
             ),
         )
 
-        setUpUdfps(previewContext, rootView)
+        setUpUdfps(previewContext, if (migrateClocksToBlueprint()) keyguardRootView else rootView)
 
         if (keyguardBottomAreaRefactor()) {
             setupShortcuts(keyguardRootView)
@@ -467,15 +472,6 @@
             return
         }
 
-        // Place the UDFPS view in the proper sensor location
-        val fingerprintLayoutParams =
-            FrameLayout.LayoutParams(sensorBounds.width(), sensorBounds.height())
-        fingerprintLayoutParams.setMarginsRelative(
-            sensorBounds.left,
-            sensorBounds.top,
-            sensorBounds.right,
-            sensorBounds.bottom
-        )
         val finger =
             LayoutInflater.from(previewContext)
                 .inflate(
@@ -483,7 +479,31 @@
                     parentView,
                     false,
                 ) as View
-        parentView.addView(finger, fingerprintLayoutParams)
+
+        // Place the UDFPS view in the proper sensor location
+        if (migrateClocksToBlueprint()) {
+            finger.id = R.id.lock_icon_view
+            parentView.addView(finger)
+            val cs = ConstraintSet()
+            cs.clone(parentView as ConstraintLayout)
+            cs.apply {
+                constrainWidth(R.id.lock_icon_view, sensorBounds.width())
+                constrainHeight(R.id.lock_icon_view, sensorBounds.height())
+                connect(R.id.lock_icon_view, TOP, PARENT_ID, TOP, sensorBounds.top)
+                connect(R.id.lock_icon_view, START, PARENT_ID, START, sensorBounds.left)
+            }
+            cs.applyTo(parentView)
+        } else {
+            val fingerprintLayoutParams =
+                FrameLayout.LayoutParams(sensorBounds.width(), sensorBounds.height())
+            fingerprintLayoutParams.setMarginsRelative(
+                sensorBounds.left,
+                sensorBounds.top,
+                sensorBounds.right,
+                sensorBounds.bottom
+            )
+            parentView.addView(finger, fingerprintLayoutParams)
+        }
     }
 
     private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index 67c42f0..00e5d35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -67,8 +67,8 @@
 @Inject
 constructor(
     private val context: Context,
-    private val biometricStatusInteractor: BiometricStatusInteractor,
-    private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+    biometricStatusInteractor: BiometricStatusInteractor,
+    deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
     private val sfpsSensorInteractor: SideFpsSensorInteractor,
     // todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
     //  DozeInteractor as DozeServiceHost already depends on DozeInteractor.
@@ -89,9 +89,6 @@
         _progress.value = 0.0f
     }
 
-    private val additionalSensorLengthPadding =
-        context.resources.getDimension(R.dimen.sfps_progress_bar_length_extra_padding).toInt()
-
     // Merged [FingerprintAuthenticationStatus] from BiometricPrompt acquired messages and
     // device entry authentication messages
     private val mergedFingerprintAuthenticationStatus =
@@ -114,9 +111,7 @@
     val progress: Flow<Float> = _progress.asStateFlow()
 
     val progressBarLength: Flow<Int> =
-        sfpsSensorInteractor.sensorLocation
-            .map { it.length + additionalSensorLengthPadding }
-            .distinctUntilChanged()
+        sfpsSensorInteractor.sensorLocation.map { it.length }.distinctUntilChanged()
 
     val progressBarThickness =
         context.resources.getDimension(R.dimen.sfps_progress_bar_thickness).toInt()
@@ -128,7 +123,6 @@
                     context.resources
                         .getDimension(R.dimen.sfps_progress_bar_padding_from_edge)
                         .toInt()
-                val lengthOfTheProgressBar = sensorLocation.length + additionalSensorLengthPadding
                 val viewLeftTop = Point(sensorLocation.left, sensorLocation.top)
                 val totalDistanceFromTheEdge = paddingFromEdge + progressBarThickness
 
@@ -139,7 +133,7 @@
                     // Sensor is vertical to the current orientation, we rotate it 270 deg
                     // around the (left,top) point as the pivot. We need to push it down the
                     // length of the progress bar so that it is still aligned to the sensor
-                    viewLeftTop.y += lengthOfTheProgressBar
+                    viewLeftTop.y += sensorLocation.length
                     val isSensorOnTheNearEdge =
                         rotation == DisplayRotation.ROTATION_180 ||
                             rotation == DisplayRotation.ROTATION_90
@@ -164,7 +158,6 @@
                         // We want to push it up from the bottom edge by the padding and
                         // the thickness of the progressbar.
                         viewLeftTop.y -= totalDistanceFromTheEdge
-                        viewLeftTop.x -= additionalSensorLengthPadding
                     }
                 }
                 viewLeftTop
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 4d55714..8ff0e36 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -992,6 +992,15 @@
         return mContainer.getQsHeight();
     }
 
+    /**
+     * Pass the size of the navbar when it's at the bottom of the device so it can be used as
+     * padding
+     * @param padding size of the bottom nav bar in px
+     */
+    public void applyBottomNavBarToCustomizerPadding(int padding) {
+        mQSCustomizerController.applyBottomNavBarSizeToRecyclerViewPadding(padding);
+    }
+
     @NeverCompile
     @Override
     public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 07705f3..f8d4080 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -65,6 +65,8 @@
     private boolean mOpening;
     private boolean mIsShowingNavBackdrop;
 
+    private boolean mSceneContainerEnabled;
+
     public QSCustomizer(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -88,6 +90,28 @@
         updateTransparentViewHeight();
     }
 
+    void applyBottomNavBarToPadding(int padding) {
+        mRecyclerView.setPadding(
+                /* left= */ mRecyclerView.getPaddingLeft(),
+                /* top= */ mRecyclerView.getPaddingTop(),
+                /* right= */ mRecyclerView.getPaddingRight(),
+                /* bottom= */ padding
+        );
+    }
+
+    void setSceneContainerEnabled(boolean enabled) {
+        if (enabled != mSceneContainerEnabled) {
+            mSceneContainerEnabled = enabled;
+            updateTransparentViewHeight();
+            if (mSceneContainerEnabled) {
+                findViewById(R.id.nav_bar_background).setVisibility(View.GONE);
+            } else {
+                findViewById(R.id.nav_bar_background)
+                        .setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE);
+            }
+        }
+    }
+
     void updateResources() {
         updateTransparentViewHeight();
         mRecyclerView.getAdapter().notifyItemChanged(0);
@@ -98,7 +122,8 @@
         mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600
                 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
         if (navBackdrop != null) {
-            navBackdrop.setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE);
+            navBackdrop.setVisibility(
+                    mIsShowingNavBackdrop && !mSceneContainerEnabled ? View.VISIBLE : View.GONE);
         }
         updateNavColors(lightBarController);
     }
@@ -275,7 +300,7 @@
 
     private void updateTransparentViewHeight() {
         LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
-        lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
+        lp.height = mSceneContainerEnabled ? 0 : QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
         mTransparentView.setLayoutParams(lp);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index c28371c..34b1b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -42,6 +42,7 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -106,7 +107,8 @@
     protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
             QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
             KeyguardStateController keyguardStateController, LightBarController lightBarController,
-            ConfigurationController configurationController, UiEventLogger uiEventLogger) {
+            ConfigurationController configurationController, UiEventLogger uiEventLogger,
+            SceneContainerFlags sceneContainerFlags) {
         super(view);
         mTileQueryHelper = tileQueryHelper;
         mQsHost = qsHost;
@@ -116,10 +118,14 @@
         mLightBarController = lightBarController;
         mConfigurationController = configurationController;
         mUiEventLogger = uiEventLogger;
+        view.setSceneContainerEnabled(sceneContainerFlags.isEnabled());
 
         mToolbar = mView.findViewById(com.android.internal.R.id.action_bar);
     }
 
+    public void applyBottomNavBarSizeToRecyclerViewPadding(int padding) {
+        mView.applyBottomNavBarToPadding(padding);
+    }
 
     @Override
     protected void onViewAttached() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 3d12eed..6e4f72d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -39,10 +39,13 @@
 import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
@@ -69,6 +72,9 @@
     /** Set the current state for QS. [state]. */
     fun setState(state: State)
 
+    /** Propagates the bottom nav bar size to [QSImpl] to be used as necessary. */
+    suspend fun applyBottomNavBarPadding(padding: Int)
+
     /** The current height of QQS in the current [qsView], or 0 if there's no view. */
     val qqsHeight: Int
 
@@ -123,6 +129,11 @@
         ::AsyncLayoutInflater,
     )
 
+    private val bottomNavBarSize =
+        MutableSharedFlow<Int>(
+            extraBufferCapacity = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
+        )
     private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED)
     private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false)
     override val isCustomizing = _isCustomizing.asStateFlow()
@@ -168,6 +179,11 @@
                     }
                 }
             }
+            launch {
+                combine(bottomNavBarSize, qsImpl.filterNotNull(), ::Pair).collect {
+                    it.second.applyBottomNavBarToCustomizerPadding(it.first)
+                }
+            }
         }
     }
 
@@ -208,6 +224,10 @@
         this.state.value = state
     }
 
+    override suspend fun applyBottomNavBarPadding(padding: Int) {
+        bottomNavBarSize.emit(padding)
+    }
+
     private fun QSImpl.applyState(state: QSSceneAdapter.State) {
         setQsVisible(state.isVisible)
         setExpanded(state.isVisible)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5686c08..cc91ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -40,6 +40,7 @@
 import android.os.Bundle;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.IndentingPrintWriter;
@@ -382,7 +383,7 @@
     private boolean mUseIncreasedHeadsUpHeight;
     private float mTranslationWhenRemoved;
     private boolean mWasChildInGroupWhenRemoved;
-    private NotificationInlineImageResolver mImageResolver;
+    private final NotificationInlineImageResolver mImageResolver;
     private BigPictureIconManager mBigPictureIconManager;
     @Nullable
     private OnExpansionChangedListener mExpansionChangedListener;
@@ -1700,13 +1701,43 @@
     }
 
     /**
-     * Constructs an ExpandableNotificationRow.
-     * @param context context passed to image resolver
+     * Constructs an ExpandableNotificationRow. Used by layout inflation.
+     *
+     * @param context passed to image resolver
      * @param attrs attributes used to initialize parent view
      */
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mImageResolver = new NotificationInlineImageResolver(context,
+        this(context, attrs, context);
+        if (com.android.systemui.Flags.notificationRowUserContext()) {
+            Log.wtf(TAG, "This constructor shouldn't be called");
+        }
+    }
+
+    /**
+     * Constructs an ExpandableNotificationRow. Used by layout inflation (with a custom {@code
+     * AsyncLayoutFactory} in {@link RowInflaterTask}.
+     *
+     * @param context context context of the view
+     * @param attrs attributes used to initialize parent view
+     * @param entry notification that the row will be associated to (determines the user for the
+     *              ImageResolver)
+     */
+    public ExpandableNotificationRow(Context context, AttributeSet attrs, NotificationEntry entry) {
+        this(context, attrs, userContextForEntry(context, entry));
+    }
+
+    private static Context userContextForEntry(Context base, NotificationEntry entry) {
+        if (base.getUserId() == entry.getSbn().getNormalizedUserId()) {
+            return base;
+        }
+        return base.createContextAsUser(
+                UserHandle.of(entry.getSbn().getNormalizedUserId()), /* flags= */ 0);
+    }
+
+    private ExpandableNotificationRow(Context sysUiContext, AttributeSet attrs,
+            Context userContext) {
+        super(sysUiContext, attrs);
+        mImageResolver = new NotificationInlineImageResolver(userContext,
                 new NotificationInlineImageCache());
         float radius = getResources().getDimension(R.dimen.notification_corner_radius_small);
         mSmallRoundness = radius / getMaxRadius();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
index c620f44..609b15e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
@@ -31,6 +31,7 @@
 import com.android.internal.widget.ImageResolver;
 import com.android.internal.widget.LocalImageResolver;
 import com.android.internal.widget.MessagingMessage;
+import com.android.systemui.Flags;
 
 import java.util.HashSet;
 import java.util.List;
@@ -66,7 +67,11 @@
      * @param imageCache The implementation of internal cache.
      */
     public NotificationInlineImageResolver(Context context, ImageCache imageCache) {
-        mContext = context.getApplicationContext();
+        if (Flags.notificationRowUserContext()) {
+            mContext = context;
+        } else {
+            mContext = context.getApplicationContext();
+        }
         mImageCache = imageCache;
 
         if (mImageCache != null) {
@@ -76,6 +81,11 @@
         updateMaxImageSizes();
     }
 
+    @VisibleForTesting
+    public Context getContext() {
+        return mContext;
+    }
+
     /**
      * Check if this resolver has its internal cache implementation.
      * @return True if has its internal cache, false otherwise.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 8e974c2..9445d56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -17,10 +17,15 @@
 package com.android.systemui.statusbar.notification.row;
 
 import android.content.Context;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.asynclayoutinflater.view.AsyncLayoutFactory;
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 
 import com.android.systemui.res.R;
@@ -55,12 +60,40 @@
             mInflateOrigin = new Throwable("inflate requested here");
         }
         mListener = listener;
-        AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
+        AsyncLayoutInflater inflater = com.android.systemui.Flags.notificationRowUserContext()
+                ? new AsyncLayoutInflater(context, new RowAsyncLayoutInflater(entry))
+                : new AsyncLayoutInflater(context);
         mEntry = entry;
         entry.setInflationTask(this);
         inflater.inflate(R.layout.status_bar_notification_row, parent, this);
     }
 
+    @VisibleForTesting
+    static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
+        private final NotificationEntry mEntry;
+
+        RowAsyncLayoutInflater(NotificationEntry entry) {
+            mEntry = entry;
+        }
+
+        @Nullable
+        @Override
+        public View onCreateView(@Nullable View parent, @NonNull String name,
+                @NonNull Context context, @NonNull AttributeSet attrs) {
+            if (name.equals(ExpandableNotificationRow.class.getName())) {
+                return new ExpandableNotificationRow(context, attrs, mEntry);
+            }
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public View onCreateView(@NonNull String name, @NonNull Context context,
+                @NonNull AttributeSet attrs) {
+            return null;
+        }
+    }
+
     @Override
     public void abort() {
         mCancelled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt
new file mode 100644
index 0000000..dd81d42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification throttle hun flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationThrottleHun {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationThrottleHun()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index d4c180d..2b0a92c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -16,11 +16,14 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
+
 import static com.android.server.notification.Flags.screenshareNotificationHiding;
 
 import android.annotation.MainThread;
 import android.app.IActivityManager;
 import android.content.Context;
+import android.database.ExecutorContentObserver;
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
 import android.os.Handler;
@@ -37,6 +40,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.ListenerSet;
+import com.android.systemui.util.settings.GlobalSettings;
 
 import java.util.concurrent.Executor;
 
@@ -50,6 +54,7 @@
     private final ArraySet<String> mExemptPackages = new ArraySet<>();
     private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
     private volatile MediaProjectionInfo mProjection;
+    boolean mDisableScreenShareProtections = false;
 
     @VisibleForTesting
     final MediaProjectionManager.Callback mMediaProjectionCallback =
@@ -58,6 +63,12 @@
                 public void onStart(MediaProjectionInfo info) {
                     Trace.beginSection("SNPC.onProjectionStart");
                     try {
+                        if (mDisableScreenShareProtections) {
+                            Log.w(LOG_TAG,
+                                    "Screen share protections disabled, ignoring projectionstart");
+                            return;
+                        }
+
                         // Only enable sensitive content protection if sharing full screen
                         // Launch cookie only set (non-null) if sharing single app/task
                         updateProjectionStateAndNotifyListeners(
@@ -81,6 +92,7 @@
     @Inject
     public SensitiveNotificationProtectionControllerImpl(
             Context context,
+            GlobalSettings settings,
             MediaProjectionManager mediaProjectionManager,
             IActivityManager activityManager,
             @Main Handler mainHandler,
@@ -89,6 +101,25 @@
             return;
         }
 
+        ExecutorContentObserver developerOptionsObserver = new ExecutorContentObserver(bgExecutor) {
+            @Override
+            public void onChange(boolean selfChange) {
+                super.onChange(selfChange);
+                boolean disableScreenShareProtections = settings.getInt(
+                        DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
+                        0) != 0;
+                mainHandler.post(() -> {
+                    mDisableScreenShareProtections = disableScreenShareProtections;
+                });
+            }
+        };
+        settings.registerContentObserver(
+                DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
+                developerOptionsObserver);
+
+        // Get current setting value
+        bgExecutor.execute(() -> developerOptionsObserver.onChange(true));
+
         bgExecutor.execute(() -> {
             ArraySet<String> exemptPackages = new ArraySet<>();
             // Exempt SystemUI
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
new file mode 100644
index 0000000..8ab563a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.button.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+
+data class ToggleButtonViewModel(
+    val isChecked: Boolean,
+    val icon: Icon,
+    val label: CharSequence,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
new file mode 100644
index 0000000..aab825fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.captioning.domain
+
+import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@VolumePanelScope
+class CaptioningAvailabilityCriteria
+@Inject
+constructor(private val captioningInteractor: CaptioningInteractor) :
+    ComponentAvailabilityCriteria {
+
+    override fun isAvailable(): Flow<Boolean> =
+        captioningInteractor.isSystemAudioCaptioningUiEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
new file mode 100644
index 0000000..92f8f22
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.captioning.ui.viewmodel
+
+import android.content.Context
+import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Volume Panel captioning UI model. */
+@VolumePanelScope
+class CaptioningViewModel
+@Inject
+constructor(
+    private val context: Context,
+    private val captioningInteractor: CaptioningInteractor,
+    @VolumePanelScope private val coroutineScope: CoroutineScope,
+) {
+
+    val buttonViewModel: StateFlow<ToggleButtonViewModel?> =
+        captioningInteractor.isSystemAudioCaptioningEnabled
+            .map { isEnabled ->
+                ToggleButtonViewModel(
+                    isChecked = isEnabled,
+                    icon =
+                        Icon.Resource(
+                            if (isEnabled) R.drawable.ic_volume_odi_captions
+                            else R.drawable.ic_volume_odi_captions_disabled,
+                            null
+                        ),
+                    label = context.getString(R.string.volume_panel_captioning_title),
+                )
+            }
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+
+    fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) {
+        coroutineScope.launch { captioningInteractor.setIsSystemAudioCaptioningEnabled(enabled) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index 1a4174a..842c323 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -21,4 +21,5 @@
 object VolumePanelComponents {
 
     const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
+    const val CAPTIONING: VolumePanelComponentKey = "captioning"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index 0f19e9f..841daf8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.dagger
 
 import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
+import com.android.systemui.volume.panel.component.captioning.CaptioningModule
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.DomainModule
@@ -44,6 +45,7 @@
             UiModule::class,
             // Components modules
             BottomBarModule::class,
+            CaptioningModule::class,
         ]
 )
 interface VolumePanelComponent {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index f785eb7..defa92d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -50,6 +50,7 @@
         @VolumePanelScope
         fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
             return setOf(
+                VolumePanelComponents.CAPTIONING,
                 VolumePanelComponents.BOTTOM_BAR,
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/BottomBar.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/BottomBar.kt
new file mode 100644
index 0000000..3ea0eac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/BottomBar.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui
+
+import javax.inject.Qualifier
+
+/**
+ * Bottom bar component key.
+ *
+ * @see com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class BottomBar()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/FooterComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/FooterComponents.kt
new file mode 100644
index 0000000..12a505d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/FooterComponents.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.systemui.volume.panel.ui
+
+import javax.inject.Qualifier
+
+/**
+ * [com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent] collection to be shown
+ * below the content.
+ *
+ * @see com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class FooterComponents()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/HeaderComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/HeaderComponents.kt
new file mode 100644
index 0000000..95be84a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/HeaderComponents.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.systemui.volume.panel.ui
+
+import javax.inject.Qualifier
+
+/**
+ * [com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent] collection to be shown
+ * above the content.
+ *
+ * @see com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class HeaderComponents()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index 1346c54..a3f052d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.volume.panel.ui
 
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
 import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 
 /** UI layer bindings module. */
 @Module
@@ -27,4 +31,26 @@
 
     @Binds
     fun bindComponentsLayoutManager(impl: DefaultComponentsLayoutManager): ComponentsLayoutManager
+
+    companion object {
+
+        @Provides
+        @VolumePanelScope
+        @HeaderComponents
+        fun provideHeaderComponents(): Collection<VolumePanelComponentKey> = setOf()
+
+        @Provides
+        @VolumePanelScope
+        @FooterComponents
+        fun provideFooterComponents(): Collection<VolumePanelComponentKey> {
+            return setOf(
+                VolumePanelComponents.CAPTIONING,
+            )
+        }
+
+        @Provides
+        @VolumePanelScope
+        @BottomBar
+        fun provideBottomBarKey(): VolumePanelComponentKey = VolumePanelComponents.BOTTOM_BAR
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
index 25a95d8..1c51236 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
@@ -20,6 +20,12 @@
 
 /** Represents components grouping into the layout. */
 data class ComponentsLayout(
+    /** Top section of the Volume Panel. It's typically shown above the [contentComponents]. */
+    val headerComponents: List<ComponentState>,
+    /** Main Volume Panel content. */
     val contentComponents: List<ComponentState>,
+    /** Bottom section of the Volume Panel. It's typically shown below the [contentComponents]. */
+    val footerComponents: List<ComponentState>,
+    /** This is a separated entity that is always visible on the bottom of the Volume Panel. */
     val bottomBarComponent: ComponentState,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
index ff485c2..7fd9c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -16,27 +16,47 @@
 
 package com.android.systemui.volume.panel.ui.layout
 
-import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.ui.BottomBar
+import com.android.systemui.volume.panel.ui.FooterComponents
+import com.android.systemui.volume.panel.ui.HeaderComponents
 import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
 import javax.inject.Inject
 
 @VolumePanelScope
-class DefaultComponentsLayoutManager @Inject constructor() : ComponentsLayoutManager {
+class DefaultComponentsLayoutManager
+@Inject
+constructor(
+    @BottomBar private val bottomBar: VolumePanelComponentKey,
+    @HeaderComponents
+    private val headerComponents: Collection<VolumePanelComponentKey> = emptyList(),
+    @FooterComponents
+    private val footerComponents: Collection<VolumePanelComponentKey> = emptyList(),
+) : ComponentsLayoutManager {
 
     override fun layout(
         volumePanelState: VolumePanelState,
         components: Collection<ComponentState>
     ): ComponentsLayout {
-        val bottomBarKey = VolumePanelComponents.BOTTOM_BAR
+        val contentComponents =
+            components.filter {
+                !headerComponents.contains(it.key) &&
+                    !footerComponents.contains(it.key) &&
+                    it.key != bottomBar
+            }
+        val headerComponents = components.filter { headerComponents.contains(it.key) }
+        val footerComponents = components.filter { footerComponents.contains(it.key) }
         return ComponentsLayout(
-            components.filter { it.key != bottomBarKey }.sortedBy { it.key },
-            components.find { it.key == bottomBarKey }
-                ?: error(
-                    "VolumePanelComponents.BOTTOM_BAR must be present in the default " +
-                        "components layout."
-                )
+            headerComponents = headerComponents.sortedBy { it.key },
+            contentComponents = contentComponents.sortedBy { it.key },
+            footerComponents = footerComponents.sortedBy { it.key },
+            bottomBarComponent = components.find { it.key == bottomBar }
+                    ?: error(
+                        "VolumePanelComponents.BOTTOM_BAR must be present in the default " +
+                            "components layout."
+                    )
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 8299acb..e006d59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -35,7 +35,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.platform.test.annotations.RequiresFlagsDisabled;
@@ -50,6 +49,7 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.view.animation.AccelerateInterpolator;
+import android.window.InputTransferToken;
 
 import androidx.test.filters.LargeTest;
 
@@ -147,7 +147,8 @@
 
         Supplier<SurfaceControlViewHost> scvhSupplier = () -> {
             mSurfaceControlViewHost = spy(new SurfaceControlViewHost(
-                    mContext, mContext.getDisplay(), new Binder(), "WindowMagnification"));
+                    mContext, mContext.getDisplay(), new InputTransferToken(),
+                    "WindowMagnification"));
             mSurfaceControlViewHosts.add(mSurfaceControlViewHost);
             return mSurfaceControlViewHost;
         };
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index 66fb63b6c3..a35a509 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -63,7 +63,6 @@
 import android.graphics.Insets;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -91,6 +90,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.widget.FrameLayout;
+import android.window.InputTransferToken;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
@@ -219,7 +219,8 @@
                 mContext, mValueAnimator);
         Supplier<SurfaceControlViewHost> scvhSupplier = () -> {
             mSurfaceControlViewHost = spy(new SurfaceControlViewHost(
-                    mContext, mContext.getDisplay(), new Binder(), "WindowMagnification"));
+                    mContext, mContext.getDisplay(), new InputTransferToken(),
+                    "WindowMagnification"));
             ViewRootImpl viewRoot = mock(ViewRootImpl.class);
             when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot);
             mSurfaceControlViewHosts.add(mSurfaceControlViewHost);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index e373143..49ba915 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -16,10 +16,9 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-
-import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
+import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.PKG;
+import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.USER_HANDLE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -40,11 +39,13 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
-import android.app.NotificationChannel;
+import android.content.Context;
 import android.graphics.Color;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -57,6 +58,7 @@
 import com.android.internal.R;
 import com.android.internal.widget.CachingIconView;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -806,6 +808,27 @@
         assertThat(row.isDrawingAppearAnimation()).isFalse();
     }
 
+    @Test
+    public void imageResolver_sameNotificationUser_usesContext() throws Exception {
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow(PKG,
+                USER_HANDLE.getUid(1234), USER_HANDLE);
+
+        assertThat(row.getImageResolver().getContext()).isSameInstanceAs(mContext);
+    }
+
+    @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT)
+    public void imageResolver_differentNotificationUser_createsUserContext() throws Exception {
+        UserHandle user = new UserHandle(33);
+        Context userContext = new SysuiTestableContext(mContext);
+        mContext.prepareCreateContextAsUser(user, userContext);
+
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow(PKG,
+                user.getUid(1234), user);
+
+        assertThat(row.getImageResolver().getContext()).isSameInstanceAs(userContext);
+    }
+
     private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
             Drawable rightIconDrawable) {
         ImageView iconView = mock(ImageView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index a6bfaa4..c717991 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -569,7 +569,10 @@
             throws Exception {
 
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
-                mContext.LAYOUT_INFLATER_SERVICE);
+                Context.LAYOUT_INFLATER_SERVICE);
+        if (com.android.systemui.Flags.notificationRowUserContext()) {
+            inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry));
+        }
         mRow = (ExpandableNotificationRow) inflater.inflate(
                 R.layout.status_bar_notification_row,
                 null /* root */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
index 98be163..7592356 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
@@ -25,6 +25,7 @@
 import com.android.server.notification.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Before
 import org.junit.Test
@@ -49,6 +50,7 @@
         controller =
             SensitiveNotificationProtectionControllerImpl(
                 mContext,
+                FakeGlobalSettings(),
                 mediaProjectionManager,
                 activityManager,
                 handler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index a1aff48..1dac642 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -22,6 +22,7 @@
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.MediaProjectionManager
 import android.platform.test.annotations.EnableFlags
+import android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS
 import android.service.notification.StatusBarNotification
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -33,6 +34,7 @@
 import com.android.systemui.util.concurrency.mockExecutorHandler
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.FakeGlobalSettings
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
@@ -61,6 +63,8 @@
     @Mock private lateinit var listener2: Runnable
     @Mock private lateinit var listener3: Runnable
 
+    private lateinit var executor: FakeExecutor
+    private lateinit var globalSettings: FakeGlobalSettings
     private lateinit var mediaProjectionCallback: MediaProjectionManager.Callback
     private lateinit var controller: SensitiveNotificationProtectionControllerImpl
 
@@ -73,18 +77,19 @@
         whenever(activityManager.bugreportWhitelistedPackages)
             .thenReturn(listOf(BUGREPORT_PACKAGE_NAME))
 
-        val executor = FakeExecutor(FakeSystemClock())
-
+        executor = FakeExecutor(FakeSystemClock())
+        globalSettings = FakeGlobalSettings()
         controller =
             SensitiveNotificationProtectionControllerImpl(
                 mContext,
+                globalSettings,
                 mediaProjectionManager,
                 activityManager,
                 mockExecutorHandler(executor),
                 executor
             )
 
-        // Process exemption processing
+        // Process pending work (getting global setting and list of exemptions)
         executor.runAllReady()
 
         // Obtain useful MediaProjectionCallback
@@ -229,6 +234,14 @@
     }
 
     @Test
+    fun isSensitiveStateActive_projectionActive_disabledViaDevOption_false() {
+        setDisabledViaDeveloperOption()
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+        assertFalse(controller.isSensitiveStateActive)
+    }
+
+    @Test
     fun shouldProtectNotification_projectionInactive_false() {
         val notificationEntry = mock(NotificationEntry::class.java)
 
@@ -294,6 +307,23 @@
         assertFalse(controller.shouldProtectNotification(notificationEntry))
     }
 
+    @Test
+    fun shouldProtectNotification_projectionActive_disabledViaDevOption_false() {
+        setDisabledViaDeveloperOption()
+        mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+        val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+        assertFalse(controller.shouldProtectNotification(notificationEntry))
+    }
+
+    private fun setDisabledViaDeveloperOption() {
+        globalSettings.putInt(DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1)
+
+        // Process pending work that gets current developer option global setting
+        executor.runAllReady()
+    }
+
     private fun setShareFullScreen() {
         whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
         whenever(mediaProjectionInfo.launchCookie).thenReturn(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
new file mode 100644
index 0000000..7c36a85
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.surfaceeffects.loadingeffect
+
+import android.graphics.Paint
+import android.graphics.RenderEffect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.model.SysUiStateTest
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_IN
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_OUT
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.MAIN
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.NOT_PLAYING
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationStateChangedCallback
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.PaintDrawCallback
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.RenderEffectDrawCallback
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LoadingEffectTest : SysUiStateTest() {
+
+    private val fakeSystemClock = FakeSystemClock()
+    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+    @Test
+    fun play_paintCallback_triggersDrawCallback() {
+        var paintFromCallback: Paint? = null
+        val drawCallback =
+            object : PaintDrawCallback {
+                override fun onDraw(loadingPaint: Paint) {
+                    paintFromCallback = loadingPaint
+                }
+            }
+        val loadingEffect =
+            LoadingEffect(
+                baseType = TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                TurbulenceNoiseAnimationConfig(),
+                paintCallback = drawCallback,
+                animationStateChangedCallback = null
+            )
+
+        fakeExecutor.execute {
+            assertThat(paintFromCallback).isNull()
+
+            loadingEffect.play()
+            fakeSystemClock.advanceTime(500L)
+
+            assertThat(paintFromCallback).isNotNull()
+        }
+    }
+
+    @Test
+    fun play_renderEffectCallback_triggersDrawCallback() {
+        var renderEffectFromCallback: RenderEffect? = null
+        val drawCallback =
+            object : RenderEffectDrawCallback {
+                override fun onDraw(loadingRenderEffect: RenderEffect) {
+                    renderEffectFromCallback = loadingRenderEffect
+                }
+            }
+        val loadingEffect =
+            LoadingEffect(
+                baseType = TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                TurbulenceNoiseAnimationConfig(),
+                renderEffectCallback = drawCallback,
+                animationStateChangedCallback = null
+            )
+
+        fakeExecutor.execute {
+            assertThat(renderEffectFromCallback).isNull()
+
+            loadingEffect.play()
+            fakeSystemClock.advanceTime(500L)
+
+            assertThat(renderEffectFromCallback).isNotNull()
+        }
+    }
+
+    @Test
+    fun play_animationStateChangesInOrder() {
+        val config = TurbulenceNoiseAnimationConfig()
+        val expectedStates = arrayOf(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING)
+        val actualStates = mutableListOf(NOT_PLAYING)
+        val stateChangedCallback =
+            object : AnimationStateChangedCallback {
+                override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
+                    actualStates.add(newState)
+                }
+            }
+        val drawCallback =
+            object : PaintDrawCallback {
+                override fun onDraw(loadingPaint: Paint) {}
+            }
+        val loadingEffect =
+            LoadingEffect(
+                baseType = TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                config,
+                paintCallback = drawCallback,
+                stateChangedCallback
+            )
+
+        val timeToAdvance =
+            config.easeInDuration + config.maxDuration + config.easeOutDuration + 100
+
+        fakeExecutor.execute {
+            loadingEffect.play()
+
+            fakeSystemClock.advanceTime(timeToAdvance.toLong())
+
+            assertThat(actualStates).isEqualTo(expectedStates)
+        }
+    }
+
+    @Test
+    fun play_alreadyPlaying_playsOnlyOnce() {
+        val config = TurbulenceNoiseAnimationConfig()
+        var numPlay = 0
+        val stateChangedCallback =
+            object : AnimationStateChangedCallback {
+                override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
+                    if (oldState == NOT_PLAYING && newState == EASE_IN) {
+                        numPlay++
+                    }
+                }
+            }
+        val drawCallback =
+            object : PaintDrawCallback {
+                override fun onDraw(loadingPaint: Paint) {}
+            }
+        val loadingEffect =
+            LoadingEffect(
+                baseType = TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                config,
+                paintCallback = drawCallback,
+                stateChangedCallback
+            )
+
+        fakeExecutor.execute {
+            assertThat(numPlay).isEqualTo(0)
+
+            loadingEffect.play()
+            loadingEffect.play()
+            loadingEffect.play()
+            loadingEffect.play()
+            loadingEffect.play()
+
+            assertThat(numPlay).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun finish_finishesLoadingEffect() {
+        val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f)
+        val drawCallback =
+            object : PaintDrawCallback {
+                override fun onDraw(loadingPaint: Paint) {}
+            }
+        var isFinished = false
+        val stateChangedCallback =
+            object : AnimationStateChangedCallback {
+                override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
+                    if (oldState == MAIN && newState == NOT_PLAYING) {
+                        isFinished = true
+                    }
+                }
+            }
+        val loadingEffect =
+            LoadingEffect(
+                baseType = TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                config,
+                paintCallback = drawCallback,
+                stateChangedCallback
+            )
+
+        fakeExecutor.execute {
+            assertThat(isFinished).isFalse()
+
+            loadingEffect.play()
+            fakeSystemClock.advanceTime(config.easeInDuration.toLong() + 500L)
+
+            assertThat(isFinished).isFalse()
+
+            loadingEffect.finish()
+
+            assertThat(isFinished).isTrue()
+        }
+    }
+
+    @Test
+    fun finish_notMainState_hasNoEffect() {
+        val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f)
+        val drawCallback =
+            object : PaintDrawCallback {
+                override fun onDraw(loadingPaint: Paint) {}
+            }
+        var isFinished = false
+        val stateChangedCallback =
+            object : AnimationStateChangedCallback {
+                override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
+                    if (oldState == MAIN && newState == NOT_PLAYING) {
+                        isFinished = true
+                    }
+                }
+            }
+        val loadingEffect =
+            LoadingEffect(
+                baseType = TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                config,
+                paintCallback = drawCallback,
+                stateChangedCallback
+            )
+
+        fakeExecutor.execute {
+            assertThat(isFinished).isFalse()
+
+            loadingEffect.finish()
+
+            assertThat(isFinished).isFalse()
+        }
+    }
+
+    @Test
+    fun getNoiseOffset_returnsNoiseOffset() {
+        val expectedNoiseOffset = arrayOf(0.1f, 0.2f, 0.3f)
+        val config =
+            TurbulenceNoiseAnimationConfig(
+                noiseOffsetX = expectedNoiseOffset[0],
+                noiseOffsetY = expectedNoiseOffset[1],
+                noiseOffsetZ = expectedNoiseOffset[2]
+            )
+        val drawCallback =
+            object : PaintDrawCallback {
+                override fun onDraw(loadingPaint: Paint) {}
+            }
+        val loadingEffect =
+            LoadingEffect(
+                baseType = TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                config,
+                paintCallback = drawCallback,
+                animationStateChangedCallback = null
+            )
+
+        assertThat(loadingEffect.getNoiseOffset()).isEqualTo(expectedNoiseOffset)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 2d76027..0358474 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -14,6 +14,7 @@
 
 package com.android.systemui;
 
+import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,12 +31,15 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.res.R;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 
 public class SysuiTestableContext extends TestableContext {
 
     @GuardedBy("mRegisteredReceivers")
     private final Set<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
+    private final Map<UserHandle, Context> mContextForUser = new HashMap<>();
 
     public SysuiTestableContext(Context base) {
         super(base);
@@ -153,4 +157,22 @@
         }
         super.unregisterReceiver(receiver);
     }
+
+    /**
+     * Sets a Context object that will be returned as the result of {@link #createContextAsUser}
+     * for a specific {@code user}.
+     */
+    public void prepareCreateContextAsUser(UserHandle user, Context context) {
+        mContextForUser.put(user, context);
+    }
+
+    @Override
+    @NonNull
+    public Context createContextAsUser(UserHandle user, int flags) {
+        Context userContext = mContextForUser.get(user);
+        if (userContext != null) {
+            return userContext;
+        }
+        return super.createContextAsUser(user, flags);
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index 8e430db..b1581d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -38,6 +38,9 @@
     private val _state = MutableStateFlow<QSSceneAdapter.State?>(null)
     val state = _state.filterNotNull()
 
+    private val _navBarPadding = MutableStateFlow<Int>(0)
+    val navBarPadding = _navBarPadding.asStateFlow()
+
     override suspend fun inflate(context: Context) {
         _view.value = inflateDelegate(context)
     }
@@ -51,4 +54,8 @@
     fun setCustomizing(value: Boolean) {
         _customizing.value = value
     }
+
+    override suspend fun applyBottomNavBarPadding(padding: Int) {
+        _navBarPadding.value = padding
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/CaptioningKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/CaptioningKosmos.kt
new file mode 100644
index 0000000..0e978f2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/CaptioningKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.view.accessibility.data.repository
+
+import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.captioningRepository by Kosmos.Fixture { FakeCaptioningRepository() }
+val Kosmos.captioningInteractor by Kosmos.Fixture { CaptioningInteractor(captioningRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/FakeCaptioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/FakeCaptioningRepository.kt
new file mode 100644
index 0000000..663aaf2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/view/accessibility/data/repository/FakeCaptioningRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.view.accessibility.data.repository
+
+import com.android.settingslib.view.accessibility.data.repository.CaptioningRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCaptioningRepository : CaptioningRepository {
+
+    private val mutableIsSystemAudioCaptioningEnabled = MutableStateFlow(false)
+    override val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
+        get() = mutableIsSystemAudioCaptioningEnabled.asStateFlow()
+
+    private val mutableIsSystemAudioCaptioningUiEnabled = MutableStateFlow(false)
+    override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
+        get() = mutableIsSystemAudioCaptioningUiEnabled.asStateFlow()
+
+    override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
+        mutableIsSystemAudioCaptioningEnabled.value = isEnabled
+    }
+
+    fun setIsSystemAudioCaptioningUiEnabled(isSystemAudioCaptioningUiEnabled: Boolean) {
+        mutableIsSystemAudioCaptioningUiEnabled.value = isSystemAudioCaptioningUiEnabled
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt
deleted file mode 100644
index 655d8f7..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt
+++ /dev/null
@@ -1,37 +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.systemui.volume.panel.ui.layout
-
-import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
-import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
-
-class FakeComponentsLayoutManager(
-    private val isBottomBar: (components: ComponentState) -> Boolean
-) : ComponentsLayoutManager {
-
-    override fun layout(
-        volumePanelState: VolumePanelState,
-        components: Collection<ComponentState>
-    ): ComponentsLayout {
-        return ComponentsLayout(
-            components
-                .filter { componentState -> !isBottomBar(componentState) }
-                .sortedBy { it.key },
-            components.find(isBottomBar)!!,
-        )
-    }
-}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 01c0074..927ddd7 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -13,11 +13,13 @@
 com.android.internal.os.Clock
 com.android.internal.os.LongArrayMultiStateCounter
 com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer
+com.android.internal.os.LongMultiStateCounter
 com.android.internal.os.MonotonicClock
 com.android.internal.os.PowerProfile
 com.android.internal.os.PowerStats
 com.android.internal.os.PowerStats$Descriptor
 com.android.internal.os.RuntimeInit
+com.android.internal.power.EnergyConsumerStats
 com.android.internal.power.ModemPowerProfile
 
 android.util.AtomicFile
@@ -54,6 +56,7 @@
 android.os.BatteryUsageStatsQuery
 android.os.Binder
 android.os.Binder$IdentitySupplier
+android.os.BluetoothBatteryStats
 android.os.Broadcaster
 android.os.Build
 android.os.BundleMerger
@@ -83,12 +86,15 @@
 android.os.ThreadLocalWorkSource
 android.os.TimestampedValue
 android.os.Trace
+android.os.UserBatteryConsumer
+android.os.UserBatteryConsumer$Builder
 android.os.UidBatteryConsumer
 android.os.UidBatteryConsumer$Builder
 android.os.UserHandle
 android.os.UserManager
 android.os.VibrationAttributes
 android.os.VibrationAttributes$Builder
+android.os.WakeLockStats
 android.os.WorkSource
 
 android.content.ClipData
@@ -159,6 +165,7 @@
 
 android.metrics.LogMaker
 
+android.view.Display
 android.view.Display$HdrCapabilities
 android.view.Display$Mode
 android.view.DisplayInfo
@@ -169,7 +176,6 @@
 android.telephony.ServiceState
 
 com.android.server.LocalServices
-com.android.server.power.stats.BatteryStatsImpl
 
 com.android.internal.util.BitUtils
 com.android.internal.util.BitwiseInputStream
@@ -192,6 +198,7 @@
 com.android.internal.util.RingBuffer
 com.android.internal.util.StringPool
 
+com.android.internal.os.BackgroundThread
 com.android.internal.os.BinderCallHeavyHitterWatcher
 com.android.internal.os.BinderDeathDispatcher
 com.android.internal.os.BinderfsStatsReader
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index f902439..015c35e 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -10,6 +10,13 @@
 }
 
 flag {
+    name: "resettable_dynamic_properties"
+    namespace: "accessibility"
+    description: "Maintains initial copies of a11yServiceInfo dynamic properties so they can reset on disconnect."
+    bug: "312386990"
+}
+
+flag {
     name: "cleanup_a11y_overlays"
     namespace: "accessibility"
     description: "Removes all attached accessibility overlays when a service is removed."
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 1d73843..b64c74e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1685,6 +1685,9 @@
     }
 
     public void resetLocked() {
+        if (Flags.resettableDynamicProperties()) {
+            mAccessibilityServiceInfo.resetDynamicallyConfigurableProperties();
+        }
         mSystemSupport.getKeyEventDispatcher().flush(this);
         try {
             // Clear the proxy in the other process so this
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c58cb72..87b57b0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -23,9 +23,9 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_FINGERPRINT;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_PACKAGE_BROADCAST_RECEIVER;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
 import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
 import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index f619ca3..44d0132 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
+
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.NonNull;
@@ -24,11 +26,10 @@
 import android.content.Context;
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
@@ -117,7 +118,7 @@
 
         // TODO(b/317250444): use MediaProjectionManagerService directly, reduces unnecessary
         //  handler, delegate, and binder death recipient
-        mProjectionManager.addCallback(mProjectionCallback, new Handler(Looper.getMainLooper()));
+        mProjectionManager.addCallback(mProjectionCallback, getContext().getMainThreadHandler());
 
         try {
             mNotificationListener.registerAsSystemService(
@@ -148,6 +149,15 @@
     }
 
     private void onProjectionStart() {
+        // TODO(b/324447419): move GlobalSettings lookup to background thread
+        boolean disableScreenShareProtections =
+                Settings.Global.getInt(getContext().getContentResolver(),
+                        DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
+        if (disableScreenShareProtections) {
+            Log.w(TAG, "Screen share protections disabled, ignoring projection start");
+            return;
+        }
+
         synchronized (mSensitiveContentProtectionLock) {
             mProjectionActive = true;
             updateAppsThatShouldBlockScreenCapture();
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 1207616dc..df5a824 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -168,6 +168,7 @@
         "pixel_system_sw_video",
         "pixel_watch",
         "platform_security",
+        "pmw",
         "power",
         "preload_safety",
         "privacy_infra_policy",
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index aa21248..53255a0 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -42,7 +42,6 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.media.audio.Flags.alarmMinVolumeZero;
-import static com.android.media.audio.Flags.bluetoothMacAddressAnonymization;
 import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
 import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
 import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
@@ -4522,8 +4521,6 @@
                 + autoPublicVolumeApiHardening());
         pw.println("\tandroid.media.audio.focusFreezeTestApi:"
                 + focusFreezeTestApi());
-        pw.println("\tcom.android.media.audio.bluetoothMacAddressAnonymization:"
-                + bluetoothMacAddressAnonymization());
         pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
                 + disablePrescaleAbsoluteVolume());
         pw.println("\tandroid.media.audiopolicy.enableFadeManagerConfiguration:"
@@ -10650,9 +10647,6 @@
     }
 
     private boolean isBluetoothPrividged() {
-        if (!bluetoothMacAddressAnonymization()) {
-            return true;
-        }
         return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.BLUETOOTH_CONNECT)
                 || Binder.getCallingUid() == Process.SYSTEM_UID;
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 3417f65..3b1c011 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -259,6 +259,7 @@
         /** used for VOL_ADJUST_VOL_UID,
          *           VOL_ADJUST_SUGG_VOL,
          *           VOL_ADJUST_STREAM_VOL,
+         *           VOL_SET_LE_AUDIO_VOL
          */
         VolumeEvent(int op, int stream, int val1, int val2, String caller) {
             mOp = op;
@@ -434,6 +435,8 @@
                             .set(MediaMetrics.Property.EVENT, "setLeAudioVolume")
                             .set(MediaMetrics.Property.INDEX, mVal1)
                             .set(MediaMetrics.Property.MAX_INDEX, mVal2)
+                            .set(MediaMetrics.Property.STREAM_TYPE,
+                                    AudioSystem.streamToString(mStream))
                             .record();
                     return;
                 case VOL_SET_AVRCP_VOL:
@@ -519,7 +522,8 @@
                             .append(" gain dB:").append(mVal2)
                             .toString();
                 case VOL_SET_LE_AUDIO_VOL:
-                    return new StringBuilder("setLeAudioVolume:")
+                    return new StringBuilder("setLeAudioVolume(stream:")
+                            .append(AudioSystem.streamToString(mStream))
                             .append(" index:").append(mVal1)
                             .append(" maxIndex:").append(mVal2)
                             .toString();
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index f51043d..0f3f807 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -470,7 +470,8 @@
                     + index + " volume=" + volume);
         }
         AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
-                AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex));
+                AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, streamType, index,
+                maxIndex, /*caller=*/null));
         try {
             mLeAudio.setVolume(volume);
         } catch (Exception e) {
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMap.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMap.java
new file mode 100644
index 0000000..f218516
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMap.java
@@ -0,0 +1,155 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * An on-memory immutable data representation of subtype.xml, which contains so-called additional
+ * {@link InputMethodSubtype}.
+ *
+ * <p>While the data structure could be also used for general purpose map from IME ID to
+ * a list of {@link InputMethodSubtype}, unlike {@link InputMethodMap} this particular data
+ * structure is currently used only around additional {@link InputMethodSubtype}, which is why this
+ * class is (still) called {@code AdditionalSubtypeMap} rather than {@code InputMethodSubtypeMap}.
+ * </p>
+ */
+final class AdditionalSubtypeMap {
+    /**
+     * An empty {@link AdditionalSubtypeMap}.
+     */
+    static final AdditionalSubtypeMap EMPTY_MAP = new AdditionalSubtypeMap(new ArrayMap<>());
+
+    @NonNull
+    private final ArrayMap<String, List<InputMethodSubtype>> mMap;
+
+    @AnyThread
+    @NonNull
+    private static AdditionalSubtypeMap createOrEmpty(
+            @NonNull ArrayMap<String, List<InputMethodSubtype>> map) {
+        return map.isEmpty() ? EMPTY_MAP : new AdditionalSubtypeMap(map);
+    }
+
+    /**
+     * Create a new instance from the given {@link ArrayMap}.
+     *
+     * <p>This method effectively creates a new copy of map.</p>
+     *
+     * @param map An {@link ArrayMap} from which {@link AdditionalSubtypeMap} is to be created.
+     * @return A {@link AdditionalSubtypeMap} that contains a new copy of {@code map}.
+     */
+    @AnyThread
+    @NonNull
+    static AdditionalSubtypeMap of(@NonNull ArrayMap<String, List<InputMethodSubtype>> map) {
+        return createOrEmpty(map);
+    }
+
+    /**
+     * Create a new instance of {@link AdditionalSubtypeMap} from an existing
+     * {@link AdditionalSubtypeMap} by removing {@code key}, or return {@code map} itself if it does
+     * not contain an entry of {@code key}.
+     *
+     * @param key The key to be removed from {@code map}.
+     * @return A new instance of {@link AdditionalSubtypeMap}, which is guaranteed to not contain
+     *         {@code key}, or {@code map} itself if it does not contain an entry of {@code key}.
+     */
+    @AnyThread
+    @NonNull
+    AdditionalSubtypeMap cloneWithRemoveOrSelf(@NonNull String key) {
+        if (isEmpty() || !containsKey(key)) {
+            return this;
+        }
+        final ArrayMap<String, List<InputMethodSubtype>> newMap = new ArrayMap<>(mMap);
+        newMap.remove(key);
+        return createOrEmpty(newMap);
+    }
+
+    /**
+     * Create a new instance of {@link AdditionalSubtypeMap} from an existing
+     * {@link AdditionalSubtypeMap} by removing {@code keys} or return {@code map} itself if it does
+     * not contain any entry for {@code keys}.
+     *
+     * @param keys Keys to be removed from {@code map}.
+     * @return A new instance of {@link AdditionalSubtypeMap}, which is guaranteed to not contain
+     *         {@code keys}, or {@code map} itself if it does not contain any entry of {@code keys}.
+     */
+    @AnyThread
+    @NonNull
+    AdditionalSubtypeMap cloneWithRemoveOrSelf(@NonNull Collection<String> keys) {
+        if (isEmpty()) {
+            return this;
+        }
+        final ArrayMap<String, List<InputMethodSubtype>> newMap = new ArrayMap<>(mMap);
+        return newMap.removeAll(keys) ? createOrEmpty(newMap) : this;
+    }
+
+    /**
+     * Create a new instance of {@link AdditionalSubtypeMap} from an existing
+     * {@link AdditionalSubtypeMap} by putting {@code key} and {@code value}.
+     *
+     * @param key Key to be put into {@code map}.
+     * @param value Value to be put into {@code map}.
+     * @return A new instance of {@link AdditionalSubtypeMap}, which is guaranteed to contain the
+     *         pair of {@code key} and {@code value}.
+     */
+    @AnyThread
+    @NonNull
+    AdditionalSubtypeMap cloneWithPut(
+            @Nullable String key, @NonNull List<InputMethodSubtype> value) {
+        final ArrayMap<String, List<InputMethodSubtype>> newMap = new ArrayMap<>(mMap);
+        newMap.put(key, value);
+        return new AdditionalSubtypeMap(newMap);
+    }
+
+    private AdditionalSubtypeMap(@NonNull ArrayMap<String, List<InputMethodSubtype>> map) {
+        mMap = map;
+    }
+
+    @AnyThread
+    @Nullable
+    List<InputMethodSubtype> get(@Nullable String key) {
+        return mMap.get(key);
+    }
+
+    @AnyThread
+    boolean containsKey(@Nullable String key) {
+        return mMap.containsKey(key);
+    }
+
+    @AnyThread
+    boolean isEmpty() {
+        return mMap.isEmpty();
+    }
+
+    @AnyThread
+    @NonNull
+    Collection<String> keySet() {
+        return mMap.keySet();
+    }
+
+    @AnyThread
+    int size() {
+        return mMap.size();
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index fba71fd..146ce17 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -108,12 +108,12 @@
      * multiple threads are not calling this method at the same time for the same {@code userId}.
      * </p>
      *
-     * @param allSubtypes {@link ArrayMap} from IME ID to additional subtype list. Passing an empty
-     *                    map deletes the file.
+     * @param allSubtypes {@link AdditionalSubtypeMap} from IME ID to additional subtype list.
+     *                    Passing an empty map deletes the file.
      * @param methodMap   {@link ArrayMap} from IME ID to {@link InputMethodInfo}.
      * @param userId      The user ID to be associated with.
      */
-    static void save(ArrayMap<String, List<InputMethodSubtype>> allSubtypes,
+    static void save(AdditionalSubtypeMap allSubtypes,
             InputMethodMap methodMap, @UserIdInt int userId) {
         final File inputMethodDir = getInputMethodDir(userId);
 
@@ -142,7 +142,7 @@
     }
 
     @VisibleForTesting
-    static void saveToFile(ArrayMap<String, List<InputMethodSubtype>> allSubtypes,
+    static void saveToFile(AdditionalSubtypeMap allSubtypes,
             InputMethodMap methodMap, AtomicFile subtypesFile) {
         // Safety net for the case that this function is called before methodMap is set.
         final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
@@ -212,24 +212,21 @@
      * multiple threads are not calling this method at the same time for the same {@code userId}.
      * </p>
      *
-     * @param allSubtypes {@link ArrayMap} from IME ID to additional subtype list. This parameter
-     *                    will be used to return the result.
-     * @param userId      The user ID to be associated with.
+     * @param userId The user ID to be associated with.
+     * @return {@link AdditionalSubtypeMap} that contains the additional {@link InputMethodSubtype}.
      */
-    static void load(@NonNull ArrayMap<String, List<InputMethodSubtype>> allSubtypes,
-            @UserIdInt int userId) {
-        allSubtypes.clear();
-
+    static AdditionalSubtypeMap load(@UserIdInt int userId) {
         final AtomicFile subtypesFile = getAdditionalSubtypeFile(getInputMethodDir(userId));
         // Not having the file means there is no additional subtype.
         if (subtypesFile.exists()) {
-            loadFromFile(allSubtypes, subtypesFile);
+            return loadFromFile(subtypesFile);
         }
+        return AdditionalSubtypeMap.EMPTY_MAP;
     }
 
     @VisibleForTesting
-    static void loadFromFile(@NonNull ArrayMap<String, List<InputMethodSubtype>> allSubtypes,
-            AtomicFile subtypesFile) {
+    static AdditionalSubtypeMap loadFromFile(AtomicFile subtypesFile) {
+        final ArrayMap<String, List<InputMethodSubtype>> allSubtypes = new ArrayMap<>();
         try (FileInputStream fis = subtypesFile.openRead()) {
             final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
             int type = parser.next();
@@ -310,5 +307,6 @@
         } catch (XmlPullParserException | IOException | NumberFormatException e) {
             Slog.w(TAG, "Error reading subtypes", e);
         }
+        return AdditionalSubtypeMap.of(allSubtypes);
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 5ab9151..b8a63cd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -286,8 +286,10 @@
     final InputManagerInternal mInputManagerInternal;
     final ImePlatformCompatUtils mImePlatformCompatUtils;
     final InputMethodDeviceConfigs mInputMethodDeviceConfigs;
-    private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
-            new ArrayMap<>();
+
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    private AdditionalSubtypeMap mAdditionalSubtypeMap = AdditionalSubtypeMap.EMPTY_MAP;
     private final UserManagerInternal mUserManagerInternal;
     private final InputMethodMenuController mMenuController;
     @NonNull private final InputMethodBindingController mBindingController;
@@ -1331,17 +1333,24 @@
 
         @Override
         public void onPackageDataCleared(String packageName, int uid) {
-            boolean changed = false;
-            for (InputMethodInfo imi : mSettings.getMethodList()) {
-                if (imi.getPackageName().equals(packageName)) {
-                    mAdditionalSubtypeMap.remove(imi.getId());
-                    changed = true;
+            synchronized (ImfLock.class) {
+                // Note that one package may implement multiple IMEs.
+                final ArrayList<String> changedImes = new ArrayList<>();
+                for (InputMethodInfo imi : mSettings.getMethodList()) {
+                    if (imi.getPackageName().equals(packageName)) {
+                        changedImes.add(imi.getId());
+                    }
                 }
-            }
-            if (changed) {
-                AdditionalSubtypeUtils.save(
-                        mAdditionalSubtypeMap, mSettings.getMethodMap(), mSettings.getUserId());
-                mChangedPackages.add(packageName);
+                final AdditionalSubtypeMap newMap =
+                        mAdditionalSubtypeMap.cloneWithRemoveOrSelf(changedImes);
+                if (newMap != mAdditionalSubtypeMap) {
+                    mAdditionalSubtypeMap = newMap;
+                    AdditionalSubtypeUtils.save(
+                            mAdditionalSubtypeMap, mSettings.getMethodMap(), mSettings.getUserId());
+                }
+                if (!changedImes.isEmpty()) {
+                    mChangedPackages.add(packageName);
+                }
             }
         }
 
@@ -1411,7 +1420,8 @@
                             Slog.i(TAG,
                                     "Input method reinstalling, clearing additional subtypes: "
                                             + imi.getComponent());
-                            mAdditionalSubtypeMap.remove(imi.getId());
+                            mAdditionalSubtypeMap =
+                                    mAdditionalSubtypeMap.cloneWithRemoveOrSelf(imi.getId());
                             AdditionalSubtypeUtils.save(mAdditionalSubtypeMap,
                                     mSettings.getMethodMap(), mSettings.getUserId());
                         }
@@ -1646,7 +1656,7 @@
         // mSettings should be created before buildInputMethodListLocked
         mSettings = InputMethodSettings.createEmptyMap(userId);
 
-        AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
+        mAdditionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
         mSwitchingController =
                 InputMethodSubtypeSwitchingController.createInstanceLocked(context,
                         mSettings.getMethodMap(), userId);
@@ -1781,7 +1791,7 @@
 
         mSettings = InputMethodSettings.createEmptyMap(newUserId);
         // Additional subtypes should be reset when the user is changed
-        AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, newUserId);
+        mAdditionalSubtypeMap = AdditionalSubtypeUtils.load(newUserId);
         final String defaultImiId = mSettings.getSelectedInputMethod();
 
         if (DEBUG) {
@@ -2014,9 +2024,7 @@
                 && directBootAwareness == DirectBootAwareness.AUTO) {
             settings = mSettings;
         } else {
-            final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                    new ArrayMap<>();
-            AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+            final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
             settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
                     directBootAwareness);
         }
@@ -4216,10 +4224,15 @@
             }
 
             if (mSettings.getUserId() == userId) {
-                if (!mSettings.setAdditionalInputMethodSubtypes(imiId, toBeAdded,
-                        mAdditionalSubtypeMap, mPackageManagerInternal, callingUid)) {
+                final var newAdditionalSubtypeMap = mSettings.getNewAdditionalSubtypeMap(
+                        imiId, toBeAdded, mAdditionalSubtypeMap, mPackageManagerInternal,
+                        callingUid);
+                if (mAdditionalSubtypeMap == newAdditionalSubtypeMap) {
                     return;
                 }
+                AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, mSettings.getMethodMap(),
+                        mSettings.getUserId());
+                mAdditionalSubtypeMap = newAdditionalSubtypeMap;
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
@@ -4229,13 +4242,15 @@
                 return;
             }
 
-            final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                    new ArrayMap<>();
-            AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+            final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
             final InputMethodSettings settings = queryInputMethodServicesInternal(mContext, userId,
                     additionalSubtypeMap, DirectBootAwareness.AUTO);
-            settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap,
-                    mPackageManagerInternal, callingUid);
+            final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
+                    imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
+            if (additionalSubtypeMap != newAdditionalSubtypeMap) {
+                AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, settings.getMethodMap(),
+                        settings.getUserId());
+            }
         }
     }
 
@@ -5070,7 +5085,7 @@
 
     @NonNull
     static InputMethodSettings queryInputMethodServicesInternal(Context context,
-            @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
+            @UserIdInt int userId, @NonNull AdditionalSubtypeMap additionalSubtypeMap,
             @DirectBootAwareness int directBootAwareness) {
         final Context userAwareContext = context.getUserId() == userId
                 ? context
@@ -5110,7 +5125,7 @@
 
     @NonNull
     static InputMethodMap filterInputMethodServices(
-            ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
+            @NonNull AdditionalSubtypeMap additionalSubtypeMap,
             List<String> enabledInputMethodList, Context userAwareContext,
             List<ResolveInfo> services) {
         final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>();
@@ -5510,9 +5525,7 @@
         if (userId == mSettings.getUserId()) {
             settings = mSettings;
         } else {
-            final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                    new ArrayMap<>();
-            AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+            final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
             settings = queryInputMethodServicesInternal(mContext, userId,
                     additionalSubtypeMap, DirectBootAwareness.AUTO);
         }
@@ -5520,9 +5533,7 @@
     }
 
     private InputMethodSettings queryMethodMapForUser(@UserIdInt int userId) {
-        final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                new ArrayMap<>();
-        AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+        final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
         return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
                 DirectBootAwareness.AUTO);
     }
@@ -6570,9 +6581,8 @@
                         nextIme = mSettings.getSelectedInputMethod();
                         nextEnabledImes = mSettings.getEnabledInputMethodList();
                     } else {
-                        final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                                new ArrayMap<>();
-                        AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+                        final AdditionalSubtypeMap additionalSubtypeMap =
+                                AdditionalSubtypeUtils.load(userId);
                         final InputMethodSettings settings = queryInputMethodServicesInternal(
                                 mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index e444db1..a558838 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -24,7 +24,6 @@
 import android.os.LocaleList;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Printer;
@@ -614,26 +613,27 @@
                 explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
     }
 
-    boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
+    @NonNull
+    AdditionalSubtypeMap getNewAdditionalSubtypeMap(@NonNull String imeId,
             @NonNull ArrayList<InputMethodSubtype> subtypes,
-            @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
+            @NonNull AdditionalSubtypeMap additionalSubtypeMap,
             @NonNull PackageManagerInternal packageManagerInternal, int callingUid) {
         final InputMethodInfo imi = mMethodMap.get(imeId);
         if (imi == null) {
-            return false;
+            return additionalSubtypeMap;
         }
         if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid,
                 imi.getPackageName())) {
-            return false;
+            return additionalSubtypeMap;
         }
 
+        final AdditionalSubtypeMap newMap;
         if (subtypes.isEmpty()) {
-            additionalSubtypeMap.remove(imi.getId());
+            newMap = additionalSubtypeMap.cloneWithRemoveOrSelf(imi.getId());
         } else {
-            additionalSubtypeMap.put(imi.getId(), subtypes);
+            newMap = additionalSubtypeMap.cloneWithPut(imi.getId(), subtypes);
         }
-        AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getUserId());
-        return true;
+        return newMap;
     }
 
     boolean setEnabledInputMethodSubtypes(@NonNull String imeId,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 5c1897d..fc99471 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -25,12 +25,15 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
+import android.chre.flags.Flags;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.Context;
 import android.content.Intent;
+import android.hardware.contexthub.ErrorCode;
 import android.hardware.contexthub.HostEndpointInfo;
+import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubManager;
 import android.hardware.location.ContextHubTransaction;
@@ -65,6 +68,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -441,9 +445,35 @@
     @ContextHubTransaction.Result
     @Override
     public int sendMessageToNanoApp(NanoAppMessage message) {
+        return doSendMessageToNanoApp(message, /* transactionCallback= */ null);
+    }
+
+    /**
+     * Sends a reliable message rom this client to a nanoapp.
+     *
+     * @param message the message to send
+     * @param transactionCallback The callback to use to confirm the delivery of the message for
+     *        reliable messages.
+     * @return the error code of sending the message
+     * @throws SecurityException if this client doesn't have permissions to send a message to the
+     * nanoapp
+     */
+    @ContextHubTransaction.Result
+    @Override
+    public int sendReliableMessageToNanoApp(NanoAppMessage message,
+            IContextHubTransactionCallback transactionCallback) {
+        return doSendMessageToNanoApp(message, transactionCallback);
+    }
+
+    /**
+     * See sendReliableMessageToNanoApp().
+     */
+    @ContextHubTransaction.Result
+    private int doSendMessageToNanoApp(NanoAppMessage message,
+            @Nullable IContextHubTransactionCallback transactionCallback) {
         ContextHubServiceUtil.checkPermissions(mContext);
 
-        int result;
+        @ContextHubTransaction.Result int result;
         if (isRegistered()) {
             int authState = mMessageChannelNanoappIdMap.getOrDefault(
                     message.getNanoAppId(), AUTHORIZATION_UNKNOWN);
@@ -462,13 +492,30 @@
                 checkNanoappPermsAsync();
             }
 
-            try {
-                result = mContextHubProxy.sendMessageToContextHub(
-                    mHostEndPointId, mAttachedContextHubInfo.getId(), message);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
-                        + mAttachedContextHubInfo.getId() + ")", e);
-                result = ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+            if (!Flags.reliableMessageImplementation() || transactionCallback == null) {
+                try {
+                    result = mContextHubProxy.sendMessageToContextHub(mHostEndPointId,
+                            mAttachedContextHubInfo.getId(), message);
+                } catch (RemoteException e) {
+                    Log.e(TAG,
+                            "RemoteException in sendMessageToNanoApp (target hub ID = "
+                                    + mAttachedContextHubInfo.getId() + ")",
+                            e);
+                    result = ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+                }
+            } else {
+                result = ContextHubTransaction.RESULT_SUCCESS;
+                ContextHubServiceTransaction transaction =
+                        mTransactionManager.createMessageTransaction(mHostEndPointId,
+                                mAttachedContextHubInfo.getId(), message, transactionCallback,
+                                getPackageName());
+                try {
+                    mTransactionManager.addTransaction(transaction);
+                } catch (IllegalStateException e) {
+                    Log.e(TAG, "Unable to add a transaction in sendMessageToNanoApp "
+                            + "(target hub ID = " + mAttachedContextHubInfo.getId() + ")", e);
+                    result = ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE;
+                }
             }
 
             ContextHubEventLogger.getInstance().logMessageToNanoapp(
@@ -569,15 +616,17 @@
     }
 
     /**
-     * Sends a message to the client associated with this object.
+     * Sends a message to the client associated with this object. This function will call
+     * onFinishedCallback when the operation is complete if the message is reliable.
      *
      * @param message the message that came from a nanoapp
      * @param nanoappPermissions permissions required to communicate with the nanoapp sending this
      *     message
      * @param messagePermissions permissions required to consume the message being delivered. These
      *     permissions are what will be attributed to the client through noteOp.
+     * @return An error from ErrorCode
      */
-    void sendMessageToClient(
+    byte sendMessageToClient(
             NanoAppMessage message,
             List<String> nanoappPermissions,
             List<String> messagePermissions) {
@@ -592,7 +641,7 @@
         if (authState == AUTHORIZATION_DENIED_GRACE_PERIOD && !messagePermissions.isEmpty()) {
             Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
                     + " in grace period and napp msg has permissions");
-            return;
+            return ErrorCode.PERMISSION_DENIED;
         }
 
         // If in the grace period, don't check permissions state since it'll cause cleanup
@@ -601,15 +650,23 @@
                 || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) {
             Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
                     + " doesn't have permission");
-            return;
+            return ErrorCode.PERMISSION_DENIED;
         }
 
-        invokeCallback(callback -> callback.onMessageFromNanoApp(message));
+        byte errorCode = invokeCallback(callback -> callback.onMessageFromNanoApp(message));
+        if (errorCode != ErrorCode.OK) {
+            return errorCode;
+        }
 
         Supplier<Intent> supplier =
                 () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, nanoAppId)
                         .putExtra(ContextHubManager.EXTRA_MESSAGE, message);
-        sendPendingIntent(supplier, nanoAppId);
+        Consumer<Byte> onFinishedCallback = (Byte error) ->
+                sendMessageDeliveryStatusToContextHub(message.getMessageSequenceNumber(), error);
+        return sendPendingIntent(supplier, nanoAppId,
+                Flags.reliableMessageImplementation() && message.isReliable()
+                        ? onFinishedCallback
+                        : null);
     }
 
     /**
@@ -873,8 +930,9 @@
      * Helper function to invoke a specified client callback, if the connection is open.
      *
      * @param consumer the consumer specifying the callback to invoke
+     * @return the ErrorCode for this operation
      */
-    private synchronized void invokeCallback(CallbackConsumer consumer) {
+    private synchronized byte invokeCallback(CallbackConsumer consumer) {
         if (mContextHubClientCallback != null) {
             try {
                 acquireWakeLock();
@@ -886,8 +944,10 @@
                                 + mHostEndPointId
                                 + ")",
                         e);
+                return ErrorCode.PERMANENT_ERROR;
             }
         }
+        return ErrorCode.OK;
     }
 
     /**
@@ -918,37 +978,81 @@
     }
 
     /**
-     * Sends an intent to any existing PendingIntent
+     * Sends an intent to any existing PendingIntent.
      *
-     * @param supplier method to create the extra Intent
+     * @param supplier method to create the extra Intent.
+     * @return the ErrorCode indicating the status of sending the intent.
+     * ErrorCode.TRANSIENT_ERROR indicates there is no intent.
      */
-    private synchronized void sendPendingIntent(Supplier<Intent> supplier) {
+    private synchronized byte sendPendingIntent(Supplier<Intent> supplier) {
         if (mPendingIntentRequest.hasPendingIntent()) {
-            doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(), this);
+            return doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(),
+                    this);
         }
+        return ErrorCode.OK;
     }
 
     /**
-     * Sends an intent to any existing PendingIntent
+     * Sends an intent to any existing PendingIntent.
      *
-     * @param supplier method to create the extra Intent
-     * @param nanoAppId the ID of the nanoapp which this event is for
+     * @param supplier method to create the extra Intent.
+     * @param nanoAppId the ID of the nanoapp which this event is for.
+     * @return the ErrorCode indicating the status of sending the intent.
+     * ErrorCode.TRANSIENT_ERROR indicates there is no intent.
      */
-    private synchronized void sendPendingIntent(Supplier<Intent> supplier, long nanoAppId) {
+    private synchronized byte sendPendingIntent(Supplier<Intent> supplier, long nanoAppId) {
+        return sendPendingIntent(supplier, nanoAppId, null);
+    }
+
+    /**
+     * Sends an intent to any existing PendingIntent. This function will set the onFinishedCallback
+     * to be called when the pending intent is sent or upon a failure.
+     *
+     * @param supplier method to create the extra Intent.
+     * @param nanoAppId the ID of the nanoapp which this event is for.
+     * @param onFinishedCallback the callback called when the operation is finished.
+     * @return the ErrorCode indicating the status of sending the intent.
+     * ErrorCode.TRANSIENT_ERROR indicates there is no intent.
+     */
+    private synchronized byte sendPendingIntent(Supplier<Intent> supplier, long nanoAppId,
+            Consumer<Byte> onFinishedCallback) {
         if (mPendingIntentRequest.hasPendingIntent()
                 && mPendingIntentRequest.getNanoAppId() == nanoAppId) {
-            doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(), this);
+            ContextHubClientBroker broker = this;
+            PendingIntent.OnFinished onFinished = new PendingIntent.OnFinished() {
+                @Override
+                public void onSendFinished(
+                        PendingIntent pendingIntent,
+                        Intent intent,
+                        int resultCode,
+                        String resultData,
+                        Bundle resultExtras) {
+                    if (onFinishedCallback != null) {
+                        onFinishedCallback.accept(resultCode == 0
+                                ? ErrorCode.OK
+                                : ErrorCode.TRANSIENT_ERROR);
+                    }
+
+                    broker.onSendFinished(pendingIntent, intent, resultCode, resultData,
+                            resultExtras);
+                }
+            };
+
+            return doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(),
+                    onFinished);
         }
+        return ErrorCode.OK;
     }
 
     /**
-     * Sends a PendingIntent with extra Intent data
+     * Sends a PendingIntent with extra Intent data.
      *
-     * @param pendingIntent the PendingIntent
-     * @param intent the extra Intent data
+     * @param pendingIntent the PendingIntent.
+     * @param intent the extra Intent data.
+     * @return the ErrorCode indicating the status of sending the intent.
      */
     @VisibleForTesting
-    void doSendPendingIntent(
+    byte doSendPendingIntent(
             PendingIntent pendingIntent,
             Intent intent,
             PendingIntent.OnFinished onFinishedCallback) {
@@ -963,6 +1067,7 @@
                     /* handler= */ null,
                     requiredPermission,
                     /* options= */ null);
+            return ErrorCode.OK;
         } catch (PendingIntent.CanceledException e) {
             mIsPendingIntentCancelled.set(true);
             // The PendingIntent is no longer valid
@@ -973,6 +1078,7 @@
                             + mHostEndPointId
                             + ")");
             close();
+            return ErrorCode.PERMANENT_ERROR;
         }
     }
 
@@ -1089,6 +1195,16 @@
         releaseWakeLock();
     }
 
+    /**
+     * Callback that arrives when direct-call message callback delivery completed.
+     * Used for reliable messages.
+     */
+    @Override
+    public void reliableMessageCallbackFinished(int messageSequenceNumber, byte errorCode) {
+        sendMessageDeliveryStatusToContextHub(messageSequenceNumber, errorCode);
+        callbackFinished();
+    }
+
     @Override
     public void onSendFinished(
             PendingIntent pendingIntent,
@@ -1148,4 +1264,18 @@
                     }
                 });
     }
+
+    private void sendMessageDeliveryStatusToContextHub(int messageSequenceNumber, byte errorCode) {
+        if (!Flags.reliableMessageImplementation()) {
+            return;
+        }
+
+        MessageDeliveryStatus status = new MessageDeliveryStatus();
+        status.messageSequenceNumber = messageSequenceNumber;
+        status.errorCode = errorCode;
+        if (mContextHubProxy.sendMessageDeliveryStatusToContextHub(mAttachedContextHubInfo.getId(),
+                status) != ContextHubTransaction.RESULT_SUCCESS) {
+            Log.e(TAG, "Failed to send the reliable message status");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
index 4de7c0c..4636a49 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
@@ -18,7 +18,9 @@
 
 import android.annotation.IntDef;
 import android.app.PendingIntent;
+import android.chre.flags.Flags;
 import android.content.Context;
+import android.hardware.contexthub.ErrorCode;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.IContextHubClient;
 import android.hardware.location.IContextHubClientCallback;
@@ -218,21 +220,27 @@
     /**
      * Handles a message sent from a nanoapp.
      *
-     * @param contextHubId the ID of the hub where the nanoapp sent the message from
+     * @param contextHubId the ID of the hub where the nanoapp sent the message from.
      * @param hostEndpointId The host endpoint ID of the client that this message is for.
-     * @param message the message send by a nanoapp
-     * @param nanoappPermissions the set of permissions the nanoapp holds
+     * @param message the message send by a nanoapp.
+     * @param nanoappPermissions the set of permissions the nanoapp holds.
      * @param messagePermissions the set of permissions that should be used for attributing
-     * permissions when this message is consumed by a client
+     *        permissions when this message is consumed by a client.
+     * @return An error from ErrorCode.
      */
-    /* package */ void onMessageFromNanoApp(
-            int contextHubId, short hostEndpointId, NanoAppMessage message,
-            List<String> nanoappPermissions, List<String> messagePermissions) {
+    /* package */ byte onMessageFromNanoApp(int contextHubId, short hostEndpointId,
+            NanoAppMessage message, List<String> nanoappPermissions,
+            List<String> messagePermissions) {
         if (DEBUG_LOG_ENABLED) {
             Log.v(TAG, "Received " + message);
         }
 
         if (message.isBroadcastMessage()) {
+            if (Flags.reliableMessageImplementation() && message.isReliable()) {
+                Log.e(TAG, "Received reliable broadcast message from " + message.getNanoAppId());
+                return ErrorCode.PERMANENT_ERROR;
+            }
+
             // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API
             // requirements.
             if (!messagePermissions.isEmpty()) {
@@ -240,21 +248,25 @@
                         + message.getNanoAppId());
             }
 
-            ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message, true);
+            ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message,
+                    /* success= */ true);
             broadcastMessage(contextHubId, message, nanoappPermissions, messagePermissions);
-        } else {
-            ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(hostEndpointId);
-            if (proxy != null) {
-                ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message,
-                                                                          true);
-                proxy.sendMessageToClient(message, nanoappPermissions, messagePermissions);
-            } else {
-                ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message,
-                                                                          false);
-                Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
-                        + hostEndpointId + ")");
-            }
+            return ErrorCode.OK;
         }
+
+        ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(hostEndpointId);
+        if (proxy == null) {
+            ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message,
+                    /* success= */ false);
+            Log.e(TAG,
+                    "Cannot send message to unregistered client (host endpoint ID = "
+                            + hostEndpointId + ")");
+            return ErrorCode.DESTINATION_NOT_FOUND;
+        }
+
+        ContextHubEventLogger.getInstance().logMessageFromNanoapp(contextHubId, message,
+                /* success= */ true);
+        return proxy.sendMessageToClient(message, nanoappPermissions, messagePermissions);
     }
 
     /**
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 08cf3f7..e196dee 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -21,6 +21,7 @@
 import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
+import android.chre.flags.Flags;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -29,6 +30,8 @@
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.SensorPrivacyManagerInternal;
+import android.hardware.contexthub.ErrorCode;
+import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
 import android.hardware.location.ContextHubTransaction;
@@ -218,6 +221,11 @@
             resetSettings();
             Log.i(TAG, "Finished Context Hub Service restart");
         }
+
+        @Override
+        public void handleMessageDeliveryStatus(MessageDeliveryStatus messageDeliveryStatus) {
+            handleMessageDeliveryStatusCallback(messageDeliveryStatus);
+        }
     }
 
     public ContextHubService(Context context, IContextHubWrapper contextHubWrapper) {
@@ -822,8 +830,8 @@
                         info.getAppId(), msg.getMsgType(), msg.getData());
 
                 IContextHubClient client = mDefaultClientMap.get(contextHubHandle);
-                success = (client.sendMessageToNanoApp(message) ==
-                        ContextHubTransaction.RESULT_SUCCESS);
+                success = client.sendMessageToNanoApp(message)
+                        == ContextHubTransaction.RESULT_SUCCESS;
             } else {
                 Log.e(TAG, "Failed to send nanoapp message - nanoapp with handle "
                         + nanoAppHandle + " does not exist.");
@@ -841,16 +849,33 @@
      * @param message the message contents
      * @param nanoappPermissions the set of permissions the nanoapp holds
      * @param messagePermissions the set of permissions that should be used for attributing
-     *     permissions when this message is consumed by a client
+     *        permissions when this message is consumed by a client
      */
-    private void handleClientMessageCallback(
-            int contextHubId,
-            short hostEndpointId,
-            NanoAppMessage message,
-            List<String> nanoappPermissions,
+    private void handleClientMessageCallback(int contextHubId, short hostEndpointId,
+            NanoAppMessage message, List<String> nanoappPermissions,
             List<String> messagePermissions) {
-        mClientManager.onMessageFromNanoApp(
-                contextHubId, hostEndpointId, message, nanoappPermissions, messagePermissions);
+        byte errorCode = mClientManager.onMessageFromNanoApp(contextHubId, hostEndpointId, message,
+                nanoappPermissions, messagePermissions);
+        if (message.isReliable() && errorCode != ErrorCode.OK) {
+            sendMessageDeliveryStatusToContextHub(contextHubId, message.getMessageSequenceNumber(),
+                    errorCode);
+        }
+    }
+
+    private void sendMessageDeliveryStatusToContextHub(int contextHubId,
+            int messageSequenceNumber, byte errorCode) {
+        if (!Flags.reliableMessageImplementation()) {
+            return;
+        }
+
+        MessageDeliveryStatus status = new MessageDeliveryStatus();
+        status.messageSequenceNumber = messageSequenceNumber;
+        status.errorCode = errorCode;
+        if (mContextHubWrapper.sendMessageDeliveryStatusToContextHub(contextHubId, status)
+                != ContextHubTransaction.RESULT_SUCCESS) {
+            Log.e(TAG, "Failed to send the reliable message status for message sequence number: "
+                    + messageSequenceNumber + " with error code: " + errorCode);
+        }
     }
 
     /**
@@ -897,6 +922,16 @@
     }
 
     /**
+     * Handles a message deliveyr status from a Context Hub.
+     *
+     * @param messageDeliveryStatus     The message delivery status to deliver.
+     */
+    private void handleMessageDeliveryStatusCallback(MessageDeliveryStatus messageDeliveryStatus) {
+        mTransactionManager.onMessageDeliveryResponse(messageDeliveryStatus.messageSequenceNumber,
+                messageDeliveryStatus.errorCode == ErrorCode.OK);
+    }
+
+    /**
      * Handles an asynchronous event from a Context Hub.
      *
      * @param contextHubId the ID of the hub the response came from
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
index a31aecb..4ee2e99 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
@@ -32,15 +32,20 @@
     @ContextHubTransaction.Type
     private final int mTransactionType;
 
-    /* The ID of the nanoapp this transaction is targeted for, null if not applicable. */
+    /** The ID of the nanoapp this transaction is targeted for, null if not applicable. */
     private final Long mNanoAppId;
 
-    /*
+    /**
      * The host package associated with this transaction.
      */
     private final String mPackage;
 
-    /*
+    /**
+     * The message sequence number associated with this transaction, null if not applicable.
+     */
+    private final Integer mMessageSequenceNumber;
+
+    /**
      * true if the transaction has already completed, false otherwise
      */
     private boolean mIsComplete = false;
@@ -50,6 +55,7 @@
         mTransactionType = type;
         mNanoAppId = null;
         mPackage = packageName;
+        mMessageSequenceNumber = null;
     }
 
     /* package */ ContextHubServiceTransaction(int id, int type, long nanoAppId,
@@ -58,6 +64,16 @@
         mTransactionType = type;
         mNanoAppId = nanoAppId;
         mPackage = packageName;
+        mMessageSequenceNumber = null;
+    }
+
+    /* package */ ContextHubServiceTransaction(int id, int type, String packageName,
+            int messageSequenceNumber) {
+        mTransactionId = id;
+        mTransactionType = type;
+        mNanoAppId = null;
+        mPackage = packageName;
+        mMessageSequenceNumber = messageSequenceNumber;
     }
 
     /**
@@ -111,6 +127,13 @@
     }
 
     /**
+     * @return the message sequence number of this transaction
+     */
+    Integer getMessageSequenceNumber() {
+        return mMessageSequenceNumber;
+    }
+
+    /**
      * Gets the timeout period as defined in IContexthub.hal
      *
      * @return the timeout of this transaction in the specified time unit
@@ -119,6 +142,8 @@
         switch (mTransactionType) {
             case ContextHubTransaction.TYPE_LOAD_NANOAPP:
                 return unit.convert(30L, TimeUnit.SECONDS);
+            case ContextHubTransaction.TYPE_RELIABLE_MESSAGE:
+                return unit.convert(1000L, TimeUnit.MILLISECONDS);
             case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
             case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
             case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
@@ -152,7 +177,11 @@
         if (mNanoAppId != null) {
             out += "appId = 0x" + Long.toHexString(mNanoAppId) + ", ";
         }
-        out += "package = " + mPackage + ")";
+        out += "package = " + mPackage;
+        if (mMessageSequenceNumber != null) {
+            out += ", messageSequenceNumber = " + mMessageSequenceNumber;
+        }
+        out += ")";
 
         return out;
     }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index f637149..33d2ff0 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -276,6 +276,8 @@
         aidlMessage.messageBody = message.getMessageBody();
         // This explicit definition is required to avoid erroneous behavior at the binder.
         aidlMessage.permissions = new String[0];
+        aidlMessage.isReliable = message.isReliable();
+        aidlMessage.messageSequenceNumber = message.getMessageSequenceNumber();
 
         return aidlMessage;
     }
@@ -306,7 +308,8 @@
             android.hardware.contexthub.ContextHubMessage message) {
         return NanoAppMessage.createMessageFromNanoApp(
                 message.nanoappId, message.messageType, message.messageBody,
-                message.hostEndPoint == HOST_ENDPOINT_BROADCAST);
+                message.hostEndPoint == HOST_ENDPOINT_BROADCAST,
+                message.isReliable, message.messageSequenceNumber);
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index e46b8c0c..b18871c 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -19,6 +19,7 @@
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.IContextHubTransactionCallback;
 import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppMessage;
 import android.hardware.location.NanoAppState;
 import android.os.RemoteException;
 import android.util.Log;
@@ -75,6 +76,11 @@
      */
     private final AtomicInteger mNextAvailableId = new AtomicInteger();
 
+    /**
+     * The next available message sequence number
+     */
+    private final AtomicInteger mNextAvailableMessageSequenceNumber = new AtomicInteger();
+
     /*
      * An executor and the future object for scheduling timeout timers
      */
@@ -309,6 +315,47 @@
     }
 
     /**
+     * Creates a transaction to send a reliable message.
+     *
+     * @param hostEndpointId      The ID of the host endpoint sending the message.
+     * @param contextHubId        The ID of the hub to send the message to.
+     * @param message             The message to send.
+     * @param transactionCallback The callback of the transactions.
+     * @param packageName         The host package associated with this transaction.
+     * @return The generated transaction.
+     */
+    /* package */ ContextHubServiceTransaction createMessageTransaction(
+            short hostEndpointId, int contextHubId, NanoAppMessage message,
+            IContextHubTransactionCallback transactionCallback, String packageName) {
+        return new ContextHubServiceTransaction(mNextAvailableId.getAndIncrement(),
+                ContextHubTransaction.TYPE_RELIABLE_MESSAGE, packageName,
+                mNextAvailableMessageSequenceNumber.getAndIncrement()) {
+            @Override
+            /* package */ int onTransact() {
+                try {
+                    message.setIsReliable(/* isReliable= */ true);
+                    message.setMessageSequenceNumber(getMessageSequenceNumber());
+
+                    return mContextHubProxy.sendMessageToContextHub(hostEndpointId, contextHubId,
+                            message);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while trying to send a reliable message", e);
+                    return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+                }
+            }
+
+            @Override
+            /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
+                try {
+                    transactionCallback.onTransactionComplete(result);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while calling client onTransactionComplete", e);
+                }
+            }
+        };
+    }
+
+    /**
      * Creates a transaction for querying for a list of nanoapps.
      *
      * @param contextHubId       the ID of the hub to query
@@ -397,6 +444,30 @@
         removeTransactionAndStartNext();
     }
 
+    /* package */
+    synchronized void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) {
+        ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+        if (transaction == null) {
+            Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+            return;
+        }
+
+        Integer transactionMessageSequenceNumber = transaction.getMessageSequenceNumber();
+        if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE
+                || transactionMessageSequenceNumber == null
+                || transactionMessageSequenceNumber != messageSequenceNumber) {
+            Log.w(TAG, "Received unexpected message transaction response (expected message "
+                    + "sequence number = "
+                    + transaction.getMessageSequenceNumber()
+                    + ", received messageSequenceNumber = " + messageSequenceNumber + ")");
+            return;
+        }
+
+        transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS :
+                        ContextHubTransaction.RESULT_FAILED_AT_HUB);
+        removeTransactionAndStartNext();
+    }
+
     /**
      * Handles a query response from a Context Hub.
      *
@@ -481,10 +552,10 @@
                     }
                 };
 
-                long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
+                long timeoutMs = transaction.getTimeout(TimeUnit.MILLISECONDS);
                 try {
-                    mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
-                        TimeUnit.SECONDS);
+                    mTimeoutFuture = mTimeoutExecutor.schedule(
+                            onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS);
                 } catch (Exception e) {
                     Log.e(TAG, "Error when schedule a timer", e);
                 }
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 9c27c22..552809b 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.chre.flags.Flags;
 import android.hardware.contexthub.HostEndpointInfo;
 import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.contexthub.NanSessionRequest;
@@ -88,18 +89,25 @@
         /**
          * Handles a message from a nanoapp to a ContextHubClient.
          *
-         * @param hostEndpointId     The host endpoint ID of the recipient.
-         * @param message            The message from the nanoapp.
-         * @param nanoappPermissions The list of permissions held by the nanoapp.
-         * @param messagePermissions The list of permissions required to receive the message.
+         * @param hostEndpointId            The host endpoint ID of the recipient.
+         * @param message                   The message from the nanoapp.
+         * @param nanoappPermissions        The list of permissions held by the nanoapp.
+         * @param messagePermissions        The list of permissions required to receive the message.
          */
         void handleNanoappMessage(short hostEndpointId, NanoAppMessage message,
                 List<String> nanoappPermissions, List<String> messagePermissions);
 
         /**
-         * Handles a restart of the service
+         * Handles a restart of the service.
          */
         void handleServiceRestart();
+
+        /**
+         * Handles a message delivery status.
+         *
+         * @param messageDeliveryStatus     The message delivery status to deliver.
+         */
+        void handleMessageDeliveryStatus(MessageDeliveryStatus messageDeliveryStatus);
     }
 
     /**
@@ -308,15 +316,25 @@
     /**
      * Sends a message to the Context Hub.
      *
-     * @param hostEndpointId The host endpoint ID of the sender.
-     * @param contextHubId   The ID of the Context Hub to send the message to.
-     * @param message        The message to send.
+     * @param hostEndpointId         The host endpoint ID of the sender.
+     * @param contextHubId           The ID of the Context Hub to send the message to.
+     * @param message                The message to send.
      * @return the result of the message sending.
      */
     @ContextHubTransaction.Result
-    public abstract int sendMessageToContextHub(
-            short hostEndpointId, int contextHubId, NanoAppMessage message)
-            throws RemoteException;
+    public abstract int sendMessageToContextHub(short hostEndpointId, int contextHubId,
+            NanoAppMessage message) throws RemoteException;
+
+    /**
+     * Sends a transaction status to the Context Hub.
+     *
+     * @param contextHubId The ID of the context hub to sent the status to.
+     * @param status       The status of the transaction.
+     * @return the result of the message sending.
+     */
+    @ContextHubTransaction.Result
+    public abstract int sendMessageDeliveryStatusToContextHub(
+            int contextHubId, MessageDeliveryStatus status);
 
     /**
      * Loads a nanoapp on the Context Hub.
@@ -443,8 +461,7 @@
             public void handleContextHubMessage(android.hardware.contexthub.ContextHubMessage msg,
                     String[] msgContentPerms) {
                 mHandler.post(() -> {
-                    mCallback.handleNanoappMessage(
-                            (short) msg.hostEndPoint,
+                    mCallback.handleNanoappMessage((short) msg.hostEndPoint,
                             ContextHubServiceUtil.createNanoAppMessage(msg),
                             new ArrayList<>(Arrays.asList(msg.permissions)),
                             new ArrayList<>(Arrays.asList(msgContentPerms)));
@@ -468,9 +485,17 @@
                 // TODO(271471342): Implement
             }
 
-            public void handleMessageDeliveryStatus(char hostEndPointId,
+            public void handleMessageDeliveryStatus(
+                    char hostEndpointId,
                     MessageDeliveryStatus messageDeliveryStatus) {
-                // TODO(b/312417087): Implement reliable message support
+                if (Flags.reliableMessageImplementation()) {
+                    mHandler.post(() -> {
+                        mCallback.handleMessageDeliveryStatus(messageDeliveryStatus);
+                    });
+                } else {
+                    Log.w(TAG, "handleMessageDeliveryStatus called when the "
+                            + "reliableMessageImplementation flag is disabled");
+                }
             }
 
             public byte[] getUuid() {
@@ -624,17 +649,35 @@
         }
 
         @ContextHubTransaction.Result
-        public int sendMessageToContextHub(
-                short hostEndpointId, int contextHubId, NanoAppMessage message)
-                throws RemoteException {
+        public int sendMessageToContextHub(short hostEndpointId, int contextHubId,
+                NanoAppMessage message) throws RemoteException {
             android.hardware.contexthub.IContextHub hub = getHub();
             if (hub == null) {
                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
             }
 
             try {
-                hub.sendMessageToHub(contextHubId,
-                        ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message));
+                var msg = ContextHubServiceUtil.createAidlContextHubMessage(
+                        hostEndpointId, message);
+                hub.sendMessageToHub(contextHubId, msg);
+                return ContextHubTransaction.RESULT_SUCCESS;
+            } catch (RemoteException | ServiceSpecificException e) {
+                return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+            } catch (IllegalArgumentException e) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+        }
+
+        @ContextHubTransaction.Result
+        public int sendMessageDeliveryStatusToContextHub(int contextHubId,
+                MessageDeliveryStatus status) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
+            try {
+                hub.sendMessageDeliveryStatusToHub(contextHubId, status);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -847,8 +890,7 @@
 
             @Override
             public void handleClientMsg(ContextHubMsg message) {
-                mCallback.handleNanoappMessage(
-                        message.hostEndPoint,
+                mCallback.handleNanoappMessage(message.hostEndPoint,
                         ContextHubServiceUtil.createNanoAppMessage(message),
                         Collections.emptyList() /* nanoappPermissions */,
                         Collections.emptyList() /* messagePermissions */);
@@ -880,8 +922,7 @@
             @Override
             public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message,
                     ArrayList<String> messagePermissions) {
-                mCallback.handleNanoappMessage(
-                        message.msg_1_0.hostEndPoint,
+                mCallback.handleNanoappMessage(message.msg_1_0.hostEndPoint,
                         ContextHubServiceUtil.createNanoAppMessage(message.msg_1_0),
                         message.permissions, messagePermissions);
             }
@@ -899,9 +940,12 @@
         }
 
         @ContextHubTransaction.Result
-        public int sendMessageToContextHub(
-                short hostEndpointId, int contextHubId, NanoAppMessage message)
-                throws RemoteException {
+        public int sendMessageToContextHub(short hostEndpointId, int contextHubId,
+                NanoAppMessage message) throws RemoteException {
+            if (message.isReliable()) {
+                Log.e(TAG, "Reliable messages are only supported with the AIDL HAL");
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
             ContextHubMsg messageToNanoApp =
                     ContextHubServiceUtil.createHidlContextHubMessage(hostEndpointId, message);
             return ContextHubServiceUtil.toTransactionResult(
@@ -909,6 +953,13 @@
         }
 
         @ContextHubTransaction.Result
+        public int sendMessageDeliveryStatusToContextHub(int contextHubId,
+                MessageDeliveryStatus status) {
+            // Only supported on the AIDL implementation.
+            return ContextHubTransaction.RESULT_FAILED_NOT_SUPPORTED;
+        }
+
+        @ContextHubTransaction.Result
         public int loadNanoapp(int contextHubId, NanoAppBinary binary,
                 int transactionId) throws RemoteException {
             android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 0ae6036..cec7a79 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -194,7 +194,7 @@
                 && mComponentName.getClassName().equals(className);
     }
 
-    public void start() {
+    public void start(boolean rebindIfDisconnected) {
         if (!mRunning) {
             if (DEBUG) {
                 Slog.d(TAG, this + ": Starting");
@@ -202,6 +202,10 @@
             mRunning = true;
             updateBinding();
         }
+        if (rebindIfDisconnected && mActiveConnection == null && shouldBind()) {
+            unbind();
+            bind();
+        }
     }
 
     public void stop() {
@@ -214,15 +218,6 @@
         }
     }
 
-    public void rebindIfDisconnected() {
-        //TODO: When we are connecting to the service, calling this will unbind and bind again.
-        // We'd better not unbind if we are connecting.
-        if (mActiveConnection == null && shouldBind()) {
-            unbind();
-            bind();
-        }
-    }
-
     private void updateBinding() {
         if (shouldBind()) {
             bind();
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 3d717a8..233a3ab 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -145,13 +145,12 @@
                                     new ComponentName(serviceInfo.packageName, serviceInfo.name),
                                     isSelfScanOnlyProvider,
                                     mUserId);
-                    proxy.start();
+                    proxy.start(/* rebindIfDisconnected= */ false);
                     mProxies.add(targetIndex++, proxy);
                     mCallback.onAddProviderService(proxy);
                 } else if (sourceIndex >= targetIndex) {
                     MediaRoute2ProviderServiceProxy proxy = mProxies.get(sourceIndex);
-                    proxy.start(); // restart the proxy if needed
-                    proxy.rebindIfDisconnected();
+                    proxy.start(/* rebindIfDisconnected= */ true); // restart the proxy if needed
                     Collections.swap(mProxies, sourceIndex, targetIndex++);
                 }
             }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 85a1315..1f7d549 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -20,6 +20,9 @@
 import static android.content.Intent.ACTION_SCREEN_OFF;
 import static android.content.Intent.ACTION_SCREEN_ON;
 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
+import static android.media.MediaRouter2.SCANNING_STATE_NOT_SCANNING;
+import static android.media.MediaRouter2.SCANNING_STATE_SCANNING_FULL;
+import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE;
 import static android.media.MediaRouter2Utils.getOriginalId;
 import static android.media.MediaRouter2Utils.getProviderId;
 
@@ -42,6 +45,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
+import android.media.MediaRouter2.ScanningState;
 import android.media.MediaRouter2Manager;
 import android.media.RouteDiscoveryPreference;
 import android.media.RouteListingPreference;
@@ -224,17 +228,27 @@
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
-        final boolean hasConfigureWifiDisplayPermission = mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
-                == PackageManager.PERMISSION_GRANTED;
+        final boolean hasConfigureWifiDisplayPermission =
+                mContext.checkCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY)
+                        == PackageManager.PERMISSION_GRANTED;
         final boolean hasModifyAudioRoutingPermission =
                 checkCallerHasModifyAudioRoutingPermission(pid, uid);
 
+        boolean hasMediaRoutingControlPermission =
+                checkMediaRoutingControlPermission(uid, pid, packageName);
+
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                registerRouter2Locked(router, uid, pid, packageName, userId,
-                        hasConfigureWifiDisplayPermission, hasModifyAudioRoutingPermission);
+                registerRouter2Locked(
+                        router,
+                        uid,
+                        pid,
+                        packageName,
+                        userId,
+                        hasConfigureWifiDisplayPermission,
+                        hasModifyAudioRoutingPermission,
+                        hasMediaRoutingControlPermission);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -254,6 +268,21 @@
         }
     }
 
+    public void updateScanningState(
+            @NonNull IMediaRouter2 router, @ScanningState int scanningState) {
+        Objects.requireNonNull(router, "router must not be null");
+        validateScanningStateValue(scanningState);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                updateScanningStateLocked(router, scanningState);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public void setDiscoveryRequestWithRouter2(@NonNull IMediaRouter2 router,
             @NonNull RouteDiscoveryPreference preference) {
         Objects.requireNonNull(router, "router must not be null");
@@ -570,24 +599,15 @@
         }
     }
 
-    public void startScan(@NonNull IMediaRouter2Manager manager) {
+    public void updateScanningState(
+            @NonNull IMediaRouter2Manager manager, @ScanningState int scanningState) {
         Objects.requireNonNull(manager, "manager must not be null");
-        final long token = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                startScanLocked(manager);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
+        validateScanningStateValue(scanningState);
 
-    public void stopScan(@NonNull IMediaRouter2Manager manager) {
-        Objects.requireNonNull(manager, "manager must not be null");
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                stopScanLocked(manager);
+                updateScanningStateLocked(manager, scanningState);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -825,7 +845,16 @@
             throw new SecurityException("Must hold MEDIA_CONTENT_CONTROL");
         }
 
-        if (PermissionChecker.checkPermissionForDataDelivery(
+        if (!checkMediaRoutingControlPermission(callerUid, callerPid, callerPackageName)) {
+            throw new SecurityException(
+                    "Must hold MEDIA_CONTENT_CONTROL or MEDIA_ROUTING_CONTROL permissions.");
+        }
+    }
+
+    @RequiresPermission(value = Manifest.permission.MEDIA_ROUTING_CONTROL, conditional = true)
+    private boolean checkMediaRoutingControlPermission(
+            int callerUid, int callerPid, @Nullable String callerPackageName) {
+        return PermissionChecker.checkPermissionForDataDelivery(
                         mContext,
                         Manifest.permission.MEDIA_ROUTING_CONTROL,
                         callerPid,
@@ -833,11 +862,8 @@
                         callerPackageName,
                         /* attributionTag */ null,
                         /* message */ "Checking permissions for registering manager in"
-                                          + " MediaRouter2ServiceImpl.")
-                != PermissionChecker.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    "Must hold MEDIA_CONTENT_CONTROL or MEDIA_ROUTING_CONTROL permissions.");
-        }
+                                + " MediaRouter2ServiceImpl.")
+                == PermissionChecker.PERMISSION_GRANTED;
     }
 
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS)
@@ -944,9 +970,15 @@
     // Start of locked methods that are used by MediaRouter2.
 
     @GuardedBy("mLock")
-    private void registerRouter2Locked(@NonNull IMediaRouter2 router, int uid, int pid,
-            @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission,
-            boolean hasModifyAudioRoutingPermission) {
+    private void registerRouter2Locked(
+            @NonNull IMediaRouter2 router,
+            int uid,
+            int pid,
+            @NonNull String packageName,
+            int userId,
+            boolean hasConfigureWifiDisplayPermission,
+            boolean hasModifyAudioRoutingPermission,
+            boolean hasMediaRoutingControlPermission) {
         final IBinder binder = router.asBinder();
         if (mAllRouterRecords.get(binder) != null) {
             Slog.w(TAG, "registerRouter2Locked: Same router already exists. packageName="
@@ -955,8 +987,16 @@
         }
 
         UserRecord userRecord = getOrCreateUserRecordLocked(userId);
-        RouterRecord routerRecord = new RouterRecord(userRecord, router, uid, pid, packageName,
-                hasConfigureWifiDisplayPermission, hasModifyAudioRoutingPermission);
+        RouterRecord routerRecord =
+                new RouterRecord(
+                        userRecord,
+                        router,
+                        uid,
+                        pid,
+                        packageName,
+                        hasConfigureWifiDisplayPermission,
+                        hasModifyAudioRoutingPermission,
+                        hasMediaRoutingControlPermission);
         try {
             binder.linkToDeath(routerRecord, 0);
         } catch (RemoteException ex) {
@@ -970,9 +1010,16 @@
                 obtainMessage(UserHandler::notifyRouterRegistered,
                         userRecord.mHandler, routerRecord));
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "registerRouter2 | package: %s, uid: %d, pid: %d, router id: %d",
-                packageName, uid, pid, routerRecord.mRouterId));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "registerRouter2 | package: %s, uid: %d, pid: %d, router id: %d,"
+                                + " hasMediaRoutingControl: %b",
+                        packageName,
+                        uid,
+                        pid,
+                        routerRecord.mRouterId,
+                        hasMediaRoutingControlPermission));
     }
 
     @GuardedBy("mLock")
@@ -1012,6 +1059,33 @@
     }
 
     @GuardedBy("mLock")
+    private void updateScanningStateLocked(
+            @NonNull IMediaRouter2 router, @ScanningState int scanningState) {
+        final IBinder binder = router.asBinder();
+        RouterRecord routerRecord = mAllRouterRecords.get(binder);
+        if (routerRecord == null) {
+            Slog.w(TAG, "Router record not found. Ignoring updateScanningState call.");
+            return;
+        }
+
+        if (scanningState == SCANNING_STATE_SCANNING_FULL
+                && !routerRecord.mHasMediaRoutingControl) {
+            throw new SecurityException("Screen off scan requires MEDIA_ROUTING_CONTROL");
+        }
+
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "updateScanningStateLocked | router: %d, packageName: %s, scanningState:"
+                            + " %d",
+                        routerRecord.mRouterId,
+                        routerRecord.mPackageName,
+                        getScanningStateString(scanningState)));
+
+        routerRecord.updateScanningState(scanningState);
+    }
+
+    @GuardedBy("mLock")
     private void setDiscoveryRequestWithRouter2Locked(@NonNull RouterRecord routerRecord,
             @NonNull RouteDiscoveryPreference discoveryRequest) {
         if (routerRecord.mDiscoveryPreference.equals(discoveryRequest)) {
@@ -1347,14 +1421,24 @@
             return;
         }
 
+        boolean hasMediaRoutingControl =
+                checkMediaRoutingControlPermission(callerUid, callerPid, callerPackageName);
+
         Slog.i(
                 TAG,
                 TextUtils.formatSimple(
                         "registerManager | callerUid: %d, callerPid: %d, callerPackage: %s,"
-                                + "targetPackageName: %s, targetUserId: %d",
-                        callerUid, callerPid, callerPackageName, targetPackageName, targetUser));
+                            + " targetPackageName: %s, targetUserId: %d, hasMediaRoutingControl:"
+                            + " %b",
+                        callerUid,
+                        callerPid,
+                        callerPackageName,
+                        targetPackageName,
+                        targetUser,
+                        hasMediaRoutingControl));
 
         UserRecord userRecord = getOrCreateUserRecordLocked(targetUser.getIdentifier());
+
         managerRecord =
                 new ManagerRecord(
                         userRecord,
@@ -1362,7 +1446,8 @@
                         callerUid,
                         callerPid,
                         callerPackageName,
-                        targetPackageName);
+                        targetPackageName,
+                        hasMediaRoutingControl);
         try {
             binder.linkToDeath(managerRecord, 0);
         } catch (RemoteException ex) {
@@ -1427,41 +1512,31 @@
     }
 
     @GuardedBy("mLock")
-    private void startScanLocked(@NonNull IMediaRouter2Manager manager) {
+    private void updateScanningStateLocked(
+            @NonNull IMediaRouter2Manager manager, @ScanningState int scanningState) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
         if (managerRecord == null) {
+            Slog.w(TAG, "Manager record not found. Ignoring updateScanningState call.");
             return;
         }
 
+        if (!managerRecord.mHasMediaRoutingControl
+                && scanningState == SCANNING_STATE_SCANNING_FULL) {
+            throw new SecurityException("Screen off scan requires MEDIA_ROUTING_CONTROL");
+        }
+
         Slog.i(
                 TAG,
                 TextUtils.formatSimple(
-                        "startScan | manager: %d, ownerPackageName: %s, targetPackageName: %s",
+                        "updateScanningState | manager: %d, ownerPackageName: %s,"
+                            + " targetPackageName: %s, scanningState: %d",
                         managerRecord.mManagerId,
                         managerRecord.mOwnerPackageName,
-                        managerRecord.mTargetPackageName));
+                        managerRecord.mTargetPackageName,
+                        getScanningStateString(scanningState)));
 
-        managerRecord.startScan();
-    }
-
-    @GuardedBy("mLock")
-    private void stopScanLocked(@NonNull IMediaRouter2Manager manager) {
-        final IBinder binder = manager.asBinder();
-        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
-        if (managerRecord == null) {
-            return;
-        }
-
-        Slog.i(
-                TAG,
-                TextUtils.formatSimple(
-                        "stopScan | manager: %d, ownerPackageName: %s, targetPackageName: %s",
-                        managerRecord.mManagerId,
-                        managerRecord.mOwnerPackageName,
-                        managerRecord.mTargetPackageName));
-
-        managerRecord.stopScan();
+        managerRecord.updateScanningState(scanningState);
     }
 
     @GuardedBy("mLock")
@@ -1738,6 +1813,24 @@
         return (int) uniqueRequestId;
     }
 
+    private static String getScanningStateString(@ScanningState int scanningState) {
+        return switch (scanningState) {
+            case SCANNING_STATE_NOT_SCANNING -> "NOT_SCANNING";
+            case SCANNING_STATE_WHILE_INTERACTIVE -> "SCREEN_ON_ONLY";
+            case SCANNING_STATE_SCANNING_FULL -> "FULL";
+            default -> "Invalid scanning state: " + scanningState;
+        };
+    }
+
+    private static void validateScanningStateValue(@ScanningState int scanningState) {
+        if (scanningState != SCANNING_STATE_NOT_SCANNING
+                && scanningState != SCANNING_STATE_WHILE_INTERACTIVE
+                && scanningState != SCANNING_STATE_SCANNING_FULL) {
+            throw new IllegalArgumentException(
+                    TextUtils.formatSimple("Scanning state %d is not valid.", scanningState));
+        }
+    }
+
     final class UserRecord {
         public final int mUserId;
         //TODO: make records private for thread-safety
@@ -1817,13 +1910,21 @@
         public final boolean mHasModifyAudioRoutingPermission;
         public final AtomicBoolean mHasBluetoothRoutingPermission;
         public final int mRouterId;
+        public final boolean mHasMediaRoutingControl;
+        public @ScanningState int mScanningState = SCANNING_STATE_NOT_SCANNING;
 
         public RouteDiscoveryPreference mDiscoveryPreference;
         @Nullable public RouteListingPreference mRouteListingPreference;
 
-        RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid,
-                String packageName, boolean hasConfigureWifiDisplayPermission,
-                boolean hasModifyAudioRoutingPermission) {
+        RouterRecord(
+                UserRecord userRecord,
+                IMediaRouter2 router,
+                int uid,
+                int pid,
+                String packageName,
+                boolean hasConfigureWifiDisplayPermission,
+                boolean hasModifyAudioRoutingPermission,
+                boolean hasMediaRoutingControl) {
             mUserRecord = userRecord;
             mPackageName = packageName;
             mSelectRouteSequenceNumbers = new ArrayList<>();
@@ -1835,6 +1936,7 @@
             mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
             mHasBluetoothRoutingPermission =
                     new AtomicBoolean(checkCallerHasBluetoothPermissions(mPid, mUid));
+            mHasMediaRoutingControl = hasMediaRoutingControl;
             mRouterId = mNextRouterOrManagerId.getAndIncrement();
         }
 
@@ -1846,6 +1948,12 @@
             return mHasModifyAudioRoutingPermission || mHasBluetoothRoutingPermission.get();
         }
 
+        public boolean isActivelyScanning() {
+            return mScanningState == SCANNING_STATE_WHILE_INTERACTIVE
+                    || mScanningState == SCANNING_STATE_SCANNING_FULL
+                    || mDiscoveryPreference.shouldPerformActiveScan();
+        }
+
         @GuardedBy("mLock")
         public void maybeUpdateSystemRoutingPermissionLocked() {
             boolean oldSystemRoutingPermissionValue = hasSystemRoutingPermission();
@@ -1877,6 +1985,18 @@
             routerDied(this);
         }
 
+        public void updateScanningState(@ScanningState int scanningState) {
+            if (mScanningState == scanningState) {
+                return;
+            }
+
+            mScanningState = scanningState;
+
+            mUserRecord.mHandler.sendMessage(
+                    obtainMessage(
+                            UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
+        }
+
         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
             pw.println(prefix + "RouterRecord");
 
@@ -2002,8 +2122,11 @@
         public final int mManagerId;
         // TODO (b/281072508): Document behaviour around nullability for mTargetPackageName.
         @Nullable public final String mTargetPackageName;
+
+        public final boolean mHasMediaRoutingControl;
         @Nullable public SessionCreationRequest mLastSessionCreationRequest;
-        public boolean mIsScanning;
+
+        public @ScanningState int mScanningState = SCANNING_STATE_NOT_SCANNING;
 
         ManagerRecord(
                 @NonNull UserRecord userRecord,
@@ -2011,7 +2134,8 @@
                 int ownerUid,
                 int ownerPid,
                 @NonNull String ownerPackageName,
-                @Nullable String targetPackageName) {
+                @Nullable String targetPackageName,
+                boolean hasMediaRoutingControl) {
             mUserRecord = userRecord;
             mManager = manager;
             mOwnerUid = ownerUid;
@@ -2019,6 +2143,7 @@
             mOwnerPackageName = ownerPackageName;
             mTargetPackageName = targetPackageName;
             mManagerId = mNextRouterOrManagerId.getAndIncrement();
+            mHasMediaRoutingControl = hasMediaRoutingControl;
         }
 
         public void dispose() {
@@ -2040,29 +2165,23 @@
             pw.println(indent + "mManagerId=" + mManagerId);
             pw.println(indent + "mOwnerUid=" + mOwnerUid);
             pw.println(indent + "mOwnerPid=" + mOwnerPid);
-            pw.println(indent + "mIsScanning=" + mIsScanning);
+            pw.println(indent + "mScanningState=" + getScanningStateString(mScanningState));
 
             if (mLastSessionCreationRequest != null) {
                 mLastSessionCreationRequest.dump(pw, indent);
             }
         }
 
-        public void startScan() {
-            if (mIsScanning) {
+        private void updateScanningState(@ScanningState int scanningState) {
+            if (mScanningState == scanningState) {
                 return;
             }
-            mIsScanning = true;
-            mUserRecord.mHandler.sendMessage(PooledLambda.obtainMessage(
-                    UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
-        }
 
-        public void stopScan() {
-            if (!mIsScanning) {
-                return;
-            }
-            mIsScanning = false;
-            mUserRecord.mHandler.sendMessage(PooledLambda.obtainMessage(
-                    UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
+            mScanningState = scanningState;
+
+            mUserRecord.mHandler.sendMessage(
+                    obtainMessage(
+                            UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
         }
 
         @Override
@@ -3103,6 +3222,13 @@
                     buildCompositeDiscoveryPreference(
                             activeRouterRecords, areManagersScanning, activelyScanningPackages);
 
+            Slog.i(
+                    TAG,
+                    TextUtils.formatSimple(
+                            "Updating composite discovery preference | preference: %s, active"
+                                    + " routers: %s",
+                            newPreference, activelyScanningPackages));
+
             if (updateScanningOnUserRecord(service, activelyScanningPackages, newPreference)) {
                 updateDiscoveryPreferenceForProviders(activelyScanningPackages);
             }
@@ -3152,7 +3278,7 @@
             for (RouterRecord activeRouterRecord : activeRouterRecords) {
                 RouteDiscoveryPreference preference = activeRouterRecord.mDiscoveryPreference;
                 preferredFeatures.addAll(preference.getPreferredFeatures());
-                if (preference.shouldPerformActiveScan()) {
+                if (activeRouterRecord.isActivelyScanning()) {
                     activeScan = true;
                     activelyScanningPackages.add(activeRouterRecord.mPackageName);
                 }
@@ -3175,33 +3301,40 @@
         private static List<RouterRecord> getIndividuallyActiveRouters(
                 MediaRouter2ServiceImpl service, List<RouterRecord> allRouterRecords) {
             if (!Flags.disableScreenOffBroadcastReceiver()
-                    && !service.mPowerManager.isInteractive()) {
+                    && !service.mPowerManager.isInteractive()
+                    && !Flags.enableScreenOffScanning()) {
                 return Collections.emptyList();
             }
 
             return allRouterRecords.stream()
                     .filter(
                             record ->
-                                    service.mActivityManager.getPackageImportance(
-                                                    record.mPackageName)
-                                            <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING)
+                                    isPackageImportanceSufficientForScanning(
+                                                    service, record.mPackageName)
+                                            || record.mScanningState
+                                                    == SCANNING_STATE_SCANNING_FULL)
                     .collect(Collectors.toList());
         }
 
         private static boolean areManagersScanning(
                 MediaRouter2ServiceImpl service, List<ManagerRecord> managerRecords) {
             if (!Flags.disableScreenOffBroadcastReceiver()
-                    && !service.mPowerManager.isInteractive()) {
+                    && !service.mPowerManager.isInteractive()
+                    && !Flags.enableScreenOffScanning()) {
                 return false;
             }
 
-            return managerRecords.stream()
-                    .anyMatch(
-                            manager ->
-                                    manager.mIsScanning
-                                            && service.mActivityManager.getPackageImportance(
-                                                            manager.mOwnerPackageName)
-                                                    <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING);
+            return managerRecords.stream().anyMatch(manager ->
+                    (manager.mScanningState == SCANNING_STATE_WHILE_INTERACTIVE
+                            && isPackageImportanceSufficientForScanning(service,
+                            manager.mOwnerPackageName))
+                            || manager.mScanningState == SCANNING_STATE_SCANNING_FULL);
+        }
+
+        private static boolean isPackageImportanceSufficientForScanning(
+                MediaRouter2ServiceImpl service, String packageName) {
+            return service.mActivityManager.getPackageImportance(packageName)
+                    <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING;
         }
 
         private MediaRoute2Provider findProvider(@Nullable String providerId) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 7dd1314..6af3480 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -43,6 +43,7 @@
 import android.media.IMediaRouterService;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter;
+import android.media.MediaRouter2.ScanningState;
 import android.media.MediaRouterClientState;
 import android.media.RemoteDisplayState;
 import android.media.RemoteDisplayState.RemoteDisplayInfo;
@@ -439,6 +440,13 @@
 
     // Binder call
     @Override
+    public void updateScanningStateWithRouter2(
+            IMediaRouter2 router, @ScanningState int scanningState) {
+        mService2.updateScanningState(router, scanningState);
+    }
+
+    // Binder call
+    @Override
     public void setDiscoveryRequestWithRouter2(IMediaRouter2 router,
             RouteDiscoveryPreference request) {
         mService2.setDiscoveryRequestWithRouter2(router, request);
@@ -574,14 +582,9 @@
 
     // Binder call
     @Override
-    public void startScan(IMediaRouter2Manager manager) {
-        mService2.startScan(manager);
-    }
-
-    // Binder call
-    @Override
-    public void stopScan(IMediaRouter2Manager manager) {
-        mService2.stopScan(manager);
+    public void updateScanningState(
+            IMediaRouter2Manager manager, @ScanningState int scanningState) {
+        mService2.updateScanningState(manager, scanningState);
     }
 
     // Binder call
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7dbe880..e7ad99a 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -44,15 +44,18 @@
 import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
+import static android.app.NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED;
 import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
 import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID;
 import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_STATUS;
+import static android.app.NotificationManager.EXTRA_NOTIFICATION_POLICY;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
@@ -1340,6 +1343,10 @@
                 nv.recycle();
                 reportUserInteraction(r);
                 mAssistants.notifyAssistantActionClicked(r, action, generatedByAssistant);
+                // Notifications that have been interacted with don't need to be lifetime extended.
+                if (lifetimeExtensionRefactor()) {
+                    r.getSbn().getNotification().flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+                }
             }
         }
 
@@ -2506,17 +2513,25 @@
             }
 
             @Override
-            void onPolicyChanged() {
+            void onPolicyChanged(Policy newPolicy) {
                 Binder.withCleanCallingIdentity(() -> {
-                    sendRegisteredOnlyBroadcast(
-                            NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED);
+                    Intent intent = new Intent(ACTION_NOTIFICATION_POLICY_CHANGED);
+                    if (android.app.Flags.modesApi()) {
+                        intent.putExtra(EXTRA_NOTIFICATION_POLICY, newPolicy);
+                    }
+                    sendRegisteredOnlyBroadcast(intent);
                     mRankingHandler.requestSort();
                 });
             }
 
             @Override
-            void onConsolidatedPolicyChanged() {
+            void onConsolidatedPolicyChanged(Policy newConsolidatedPolicy) {
                 Binder.withCleanCallingIdentity(() -> {
+                    if (android.app.Flags.modesApi()) {
+                        Intent intent = new Intent(ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED);
+                        intent.putExtra(EXTRA_NOTIFICATION_POLICY, newConsolidatedPolicy);
+                        sendRegisteredOnlyBroadcast(intent);
+                    }
                     mRankingHandler.requestSort();
                 });
             }
@@ -2934,15 +2949,19 @@
     }
 
     private void sendRegisteredOnlyBroadcast(String action) {
+        sendRegisteredOnlyBroadcast(new Intent(action));
+    }
+
+    private void sendRegisteredOnlyBroadcast(Intent baseIntent) {
         int[] userIds = mUmInternal.getProfileIds(mAmi.getCurrentUserId(), true);
-        Intent intent = new Intent(action).addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        Intent intent = new Intent(baseIntent).addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         for (int userId : userIds) {
             getContext().sendBroadcastAsUser(intent, UserHandle.of(userId), null);
         }
         // explicitly send the broadcast to all DND packages, even if they aren't currently running
         for (int userId : userIds) {
             for (String pkg : mConditionProviders.getAllowedPackages(userId)) {
-                Intent pkgIntent = new Intent(action).setPackage(pkg).setFlags(
+                Intent pkgIntent = new Intent(baseIntent).setPackage(pkg).setFlags(
                         Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                 getContext().sendBroadcastAsUser(pkgIntent, UserHandle.of(userId));
             }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 2f20bbe..aebd28a 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1716,10 +1716,10 @@
             ZenLog.traceConfig(reason, mConfig, config);
 
             // send some broadcasts
-            final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
-                    getNotificationPolicy(config));
+            Policy newPolicy = getNotificationPolicy(config);
+            boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig), newPolicy);
             if (policyChanged) {
-                dispatchOnPolicyChanged();
+                dispatchOnPolicyChanged(newPolicy);
             }
             updateConfigAndZenModeLocked(config, origin, reason, setRingerMode, callingUid);
             mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/);
@@ -1929,7 +1929,7 @@
             Policy newPolicy = mConfig.toNotificationPolicy(policy);
             if (!Objects.equals(mConsolidatedPolicy, newPolicy)) {
                 mConsolidatedPolicy = newPolicy;
-                dispatchOnConsolidatedPolicyChanged();
+                dispatchOnConsolidatedPolicyChanged(newPolicy);
                 ZenLog.traceSetConsolidatedZenPolicy(mConsolidatedPolicy, reason);
             }
 
@@ -2097,15 +2097,15 @@
         }
     }
 
-    private void dispatchOnPolicyChanged() {
+    private void dispatchOnPolicyChanged(Policy newPolicy) {
         for (Callback callback : mCallbacks) {
-            callback.onPolicyChanged();
+            callback.onPolicyChanged(newPolicy);
         }
     }
 
-    private void dispatchOnConsolidatedPolicyChanged() {
+    private void dispatchOnConsolidatedPolicyChanged(Policy newConsolidatedPolicy) {
         for (Callback callback : mCallbacks) {
-            callback.onConsolidatedPolicyChanged();
+            callback.onConsolidatedPolicyChanged(newConsolidatedPolicy);
         }
     }
 
@@ -2631,8 +2631,8 @@
     public static class Callback {
         void onConfigChanged() {}
         void onZenModeChanged() {}
-        void onPolicyChanged() {}
-        void onConsolidatedPolicyChanged() {}
+        void onPolicyChanged(Policy newPolicy) {}
+        void onConsolidatedPolicyChanged(Policy newConsolidatedPolicy) {}
         void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {}
     }
 }
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 79d1753..1dd7905 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -50,6 +50,7 @@
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.SELinuxUtil;
 
 import dalvik.system.VMRuntime;
@@ -502,6 +503,7 @@
     private void assertPackageStorageValid(@NonNull Computer snapshot, String volumeUuid,
             String packageName, int userId) throws PackageManagerException {
         final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
+        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
         if (packageState == null) {
             throw PackageManagerException.ofInternalError("Package " + packageName + " is unknown",
                     PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_PACKAGE_UNKNOWN);
@@ -510,9 +512,10 @@
                     "Package " + packageName + " found on unknown volume " + volumeUuid
                             + "; expected volume " + packageState.getVolumeUuid(),
                     PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_VOLUME_UNKNOWN);
-        } else if (!packageState.getUserStateOrDefault(userId).isInstalled()) {
+        } else if (!userState.isInstalled() && !userState.dataExists()) {
             throw PackageManagerException.ofInternalError(
-                    "Package " + packageName + " not installed for user " + userId,
+                    "Package " + packageName + " not installed for user " + userId
+                            + " or was deleted without DELETE_KEEP_DATA",
                     PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_NOT_INSTALLED_FOR_USER);
         } else if (packageState.getPkg() != null
                 && !shouldHaveAppStorage(packageState.getPkg())) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 89589ed..4fb9b56 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4801,7 +4801,7 @@
         try {
             final ComponentName domainVerificationAgent = mInterface.getDomainVerificationAgent();
             pw.println(domainVerificationAgent == null
-                    ? "No Domain Verifier available!" : domainVerificationAgent.toString());
+                    ? "No Domain Verifier available!" : domainVerificationAgent.flattenToString());
         } catch (Exception e) {
             pw.println("Failure [" + e.getMessage() + "]");
             return 1;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index f1dca77..f44fcf0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -65,6 +65,7 @@
 import android.permission.IPermissionManager;
 import android.permission.PermissionCheckerManager;
 import android.permission.PermissionManager;
+import android.permission.PermissionManager.PermissionState;
 import android.permission.PermissionManagerInternal;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.util.ArrayMap;
@@ -258,6 +259,13 @@
     }
 
     @Override
+    public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
+            @NonNull String persistentDeviceId, int userId) {
+        return mPermissionManagerServiceImpl.getAllPermissionStates(packageName,
+                persistentDeviceId, userId);
+    }
+
+    @Override
     public boolean setAutoRevokeExempted(
             @NonNull String packageName, boolean exempted, int userId) {
         Objects.requireNonNull(packageName);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 9afd36f..c5b637d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -999,6 +999,13 @@
         return checkUidPermissionInternal(pkg, uid, permName);
     }
 
+    @Override
+    public Map<String, PermissionManager.PermissionState> getAllPermissionStates(
+            @NonNull String packageName, @NonNull String persistentDeviceId, int userId) {
+        throw new UnsupportedOperationException(
+                "This method is supported in newer implementation only");
+    }
+
     /**
      * Checks whether or not the given package has been granted the specified
      * permission. If the given package is {@code null}, we instead check the
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index b12d8ac..7c10425 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -25,6 +25,7 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.permission.IOnPermissionsChangeListener;
+import android.permission.PermissionManager.PermissionState;
 import android.permission.PermissionManagerInternal;
 
 import com.android.server.pm.pkg.AndroidPackage;
@@ -406,6 +407,19 @@
     int checkUidPermission(int uid, String permName, int deviceId);
 
     /**
+     * Gets the permission states for requested package, persistent device and user.
+     *
+     * @param packageName name of the package you are checking against
+     * @param persistentDeviceId id of the persistent device you are checking against
+     * @param userId id of the user for which to get permission flags
+     * @return mapping of all permission states keyed by their permission names
+     *
+     * @hide
+     */
+    Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
+            @NonNull String persistentDeviceId, @UserIdInt int userId);
+
+    /**
      * Get all the package names requesting app op permissions.
      *
      * @return a map of app op permission names to package names requesting them
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
index 835ddcb..91a778d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
@@ -22,6 +22,7 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.permission.IOnPermissionsChangeListener;
+import android.permission.PermissionManager.PermissionState;
 import android.util.Log;
 
 import com.android.server.pm.pkg.AndroidPackage;
@@ -243,12 +244,20 @@
 
     @Override
     public int checkUidPermission(int uid, String permName, int deviceId) {
-        Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName
-                + ", deviceId = " + deviceId + ")");
+        Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = "
+                + permName + ", deviceId = " + deviceId + ")");
         return mService.checkUidPermission(uid, permName, deviceId);
     }
 
     @Override
+    public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
+            @NonNull String persistentDeviceId, int userId) {
+        Log.i(LOG_TAG, "getAllPermissionStates(packageName = " + packageName
+                + ", persistentDeviceId = " + persistentDeviceId + ", userId = " + userId + ")");
+        return mService.getAllPermissionStates(packageName, persistentDeviceId, userId);
+    }
+
+    @Override
     public Map<String, Set<String>> getAllAppOpPermissionPackages() {
         Log.i(LOG_TAG, "getAllAppOpPermissionPackages()");
         return mService.getAllAppOpPermissionPackages();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
index 66a6f3c..0a4ff07 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
@@ -24,6 +24,7 @@
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.os.Build;
 import android.permission.IOnPermissionsChangeListener;
+import android.permission.PermissionManager.PermissionState;
 
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
@@ -327,6 +328,12 @@
     }
 
     @Override
+    public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
+            @NonNull String persistentDeviceId, int userId) {
+        return mNewImplementation.getAllPermissionStates(packageName, persistentDeviceId, userId);
+    }
+
+    @Override
     public Map<String, Set<String>> getAllAppOpPermissionPackages() {
         Map<String, Set<String>> oldVal = mOldImplementation.getAllAppOpPermissionPackages();
         Map<String, Set<String>> newVal = mNewImplementation.getAllAppOpPermissionPackages();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
index f21993c..bc29e67 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
@@ -23,6 +23,7 @@
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.os.Trace;
 import android.permission.IOnPermissionsChangeListener;
+import android.permission.PermissionManager.PermissionState;
 
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
@@ -348,6 +349,18 @@
     }
 
     @Override
+    public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
+            @NonNull String persistentDeviceId, int userId) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl"
+                + "#getAllPermissionStates");
+        try {
+            return mService.getAllPermissionStates(packageName, persistentDeviceId, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
     public Map<String, Set<String>> getAllAppOpPermissionPackages() {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#getAllAppOpPermissionPackages");
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
index 3f00a9d..7d90240 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
@@ -26,6 +26,7 @@
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.verify.domain.DomainVerificationUserState;
 import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.os.Bundle;
 import android.os.ServiceSpecificException;
 
 import java.util.List;
@@ -41,6 +42,27 @@
         mService = service;
     }
 
+    @Override
+    public void setUriRelativeFilterGroups(@NonNull String packageName,
+            @NonNull Bundle domainToGroupsBundle) {
+        try {
+            mService.setUriRelativeFilterGroups(packageName, domainToGroupsBundle);
+        } catch (Exception e) {
+            throw rethrow(e);
+        }
+    }
+
+    @NonNull
+    @Override
+    public Bundle getUriRelativeFilterGroups(
+            @NonNull String packageName, @NonNull List<String> domains) {
+        try {
+            return mService.getUriRelativeFilterGroups(packageName, domains);
+        } catch (Exception e) {
+            throw rethrow(e);
+        }
+    }
+
     @NonNull
     @Override
     public List<String> queryValidVerificationPackageNames() {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
index ac6d795..de464a4 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.UriRelativeFilter;
+import android.content.UriRelativeFilterGroup;
 import android.content.pm.Signature;
 import android.content.pm.verify.domain.DomainVerificationState;
 import android.os.UserHandle;
@@ -38,7 +40,10 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
 import java.util.UUID;
 import java.util.function.Function;
 
@@ -67,6 +72,13 @@
     public static final String TAG_DOMAIN = "domain";
     public static final String ATTR_NAME = "name";
     public static final String ATTR_STATE = "state";
+    public static final String TAG_URI_RELATIVE_FILTER_GROUPS = "uri-relative-filter-groups";
+    public static final String TAG_URI_RELATIVE_FILTER_GROUP = "uri-relative-filter-group";
+    public static final String ATTR_ACTION = "action";
+    public static final String TAG_URI_RELATIVE_FILTER = "uri-relative-filter";
+    public static final String ATTR_URI_PART = "uri-part";
+    public static final String ATTR_PATTERN_TYPE = "pattern-type";
+    public static final String ATTR_FILTER = "filter";
 
     /**
      * @param pkgNameToSignature Converts package name to a string representation of its signature.
@@ -176,6 +188,7 @@
 
         final ArrayMap<String, Integer> stateMap = new ArrayMap<>();
         final SparseArray<DomainVerificationInternalUserState> userStates = new SparseArray<>();
+        final ArrayMap<String, List<UriRelativeFilterGroup>> groupMap = new ArrayMap<>();
 
         SettingsXml.ChildSection child = section.children();
         while (child.moveToNext()) {
@@ -186,11 +199,47 @@
                 case TAG_USER_STATES:
                     readUserStates(child, userStates);
                     break;
+                case TAG_URI_RELATIVE_FILTER_GROUPS:
+                    readUriRelativeFilterGroups(child, groupMap);
+                    break;
             }
         }
 
         return new DomainVerificationPkgState(packageName, id, hasAutoVerifyDomains, stateMap,
-                userStates, signature);
+                userStates, signature, groupMap);
+    }
+
+    private static void readUriRelativeFilterGroups(@NonNull SettingsXml.ReadSection section,
+            @NonNull ArrayMap<String, List<UriRelativeFilterGroup>> groupMap) {
+        SettingsXml.ChildSection child = section.children();
+        while (child.moveToNext(TAG_DOMAIN)) {
+            String domain = child.getString(ATTR_NAME);
+            groupMap.put(domain, createUriRelativeFilterGroupsFromXml(child));
+        }
+    }
+
+    private static ArrayList<UriRelativeFilterGroup> createUriRelativeFilterGroupsFromXml(
+            @NonNull SettingsXml.ReadSection section) {
+        SettingsXml.ChildSection child = section.children();
+        ArrayList<UriRelativeFilterGroup> groups = new ArrayList<>();
+        while (child.moveToNext(TAG_URI_RELATIVE_FILTER_GROUP)) {
+            UriRelativeFilterGroup group = new UriRelativeFilterGroup(section.getInt(ATTR_ACTION));
+            readUriRelativeFiltersFromXml(child, group);
+            groups.add(group);
+        }
+        return groups;
+    }
+
+    private static void readUriRelativeFiltersFromXml(
+            @NonNull SettingsXml.ReadSection section, UriRelativeFilterGroup group) {
+        SettingsXml.ChildSection child = section.children();
+        while (child.moveToNext(TAG_URI_RELATIVE_FILTER)) {
+            String filter = child.getString(ATTR_FILTER);
+            if (filter != null) {
+                group.addUriRelativeFilter(new UriRelativeFilter(child.getInt(ATTR_URI_PART),
+                        child.getInt(ATTR_PATTERN_TYPE), filter));
+            }
+        }
     }
 
     private static void readUserStates(@NonNull SettingsXml.ReadSection section,
@@ -236,6 +285,7 @@
                              .attribute(ATTR_SIGNATURE, signature)) {
             writeStateMap(parentSection, pkgState.getStateMap());
             writeUserStates(parentSection, userId, pkgState.getUserStates());
+            writeUriRelativeFilterGroupMap(parentSection, pkgState.getUriRelativeFilterGroupMap());
         }
     }
 
@@ -334,6 +384,52 @@
         }
     }
 
+    private static void writeUriRelativeFilterGroupMap(
+            @NonNull SettingsXml.WriteSection parentSection,
+            @NonNull ArrayMap<String, List<UriRelativeFilterGroup>> groupMap) throws IOException {
+        if (groupMap.isEmpty()) {
+            return;
+        }
+        try (SettingsXml.WriteSection section =
+                     parentSection.startSection(TAG_URI_RELATIVE_FILTER_GROUPS)) {
+            for (int i = 0; i < groupMap.size(); i++) {
+                writeUriRelativeFilterGroups(section, groupMap.keyAt(i), groupMap.valueAt(i));
+            }
+        }
+    }
+
+    private static void writeUriRelativeFilterGroups(
+            @NonNull SettingsXml.WriteSection parentSection, @NonNull String domain,
+            @NonNull List<UriRelativeFilterGroup> groups) throws IOException {
+        if (groups.isEmpty()) {
+            return;
+        }
+        try (SettingsXml.WriteSection section =
+                     parentSection.startSection(TAG_DOMAIN)
+                             .attribute(ATTR_NAME, domain)) {
+            for (int i = 0; i < groups.size(); i++) {
+                writeUriRelativeFilterGroup(section, groups.get(i));
+            }
+        }
+    }
+
+    private static void writeUriRelativeFilterGroup(
+            @NonNull SettingsXml.WriteSection parentSection,
+            @NonNull UriRelativeFilterGroup group) throws IOException {
+        try (SettingsXml.WriteSection section =
+                     parentSection.startSection(TAG_URI_RELATIVE_FILTER_GROUP)
+                             .attribute(ATTR_ACTION, group.getAction())) {
+            Iterator<UriRelativeFilter> it = group.getUriRelativeFilters().iterator();
+            while (it.hasNext()) {
+                UriRelativeFilter filter = it.next();
+                section.startSection(TAG_URI_RELATIVE_FILTER)
+                        .attribute(ATTR_URI_PART, filter.getUriPart())
+                        .attribute(ATTR_PATTERN_TYPE, filter.getPatternType())
+                        .attribute(ATTR_FILTER, filter.getFilter()).finish();
+            }
+        }
+    }
+
     public static class ReadResult {
 
         @NonNull
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index c796b40..305b087 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -19,14 +19,19 @@
 import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
 
+import android.Manifest;
 import android.annotation.CheckResult;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.compat.annotation.ChangeId;
 import android.content.Context;
 import android.content.Intent;
+import android.content.UriRelativeFilterGroup;
+import android.content.UriRelativeFilterGroupParcel;
+import android.content.pm.Flags;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -38,6 +43,8 @@
 import android.content.pm.verify.domain.DomainVerificationState;
 import android.content.pm.verify.domain.DomainVerificationUserState;
 import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.net.Uri;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -223,6 +230,72 @@
         mProxy = proxy;
     }
 
+    /**
+     * Update the URI relative filter groups for a package's verified domains. All previously
+     * existing groups will be cleared before the new groups will be applied.
+     */
+    @RequiresPermission(Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+    public void setUriRelativeFilterGroups(@NonNull String packageName,
+            @NonNull Bundle bundle)
+            throws NameNotFoundException {
+        getContext().enforceCallingOrSelfPermission(
+                android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+                "Caller " + mConnection.getCallingUid() + " does not hold "
+                        + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT);
+        if (bundle.isEmpty()) {
+            return;
+        }
+        synchronized (mLock) {
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState == null) {
+                throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+            }
+            Map<String, List<UriRelativeFilterGroup>> domainToGroupsMap =
+                    pkgState.getUriRelativeFilterGroupMap();
+            for (String domain : bundle.keySet()) {
+                ArrayList<UriRelativeFilterGroupParcel> parcels =
+                        bundle.getParcelableArrayList(domain, UriRelativeFilterGroupParcel.class);
+                domainToGroupsMap.put(domain, UriRelativeFilterGroup.parcelsToGroups(parcels));
+            }
+        }
+    }
+
+    /**
+     * Retrieve the current URI relative filter groups for a package's verified domain.
+     */
+    @NonNull
+    public Bundle getUriRelativeFilterGroups(@NonNull String packageName,
+            @NonNull List<String> domains) {
+        Bundle bundle = new Bundle();
+        synchronized (mLock) {
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState != null) {
+                Map<String, List<UriRelativeFilterGroup>> map =
+                        pkgState.getUriRelativeFilterGroupMap();
+                for (int i = 0; i < domains.size(); i++) {
+                    List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
+                    bundle.putParcelableList(domains.get(i),
+                            UriRelativeFilterGroup.groupsToParcels(groups));
+                }
+            }
+        }
+        return bundle;
+    }
+
+    @NonNull
+    private List<UriRelativeFilterGroup> getUriRelativeFilterGroups(@NonNull String packageName,
+            @NonNull String domain) {
+        List<UriRelativeFilterGroup> groups = Collections.emptyList();
+        synchronized (mLock) {
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState != null) {
+                groups = pkgState.getUriRelativeFilterGroupMap().getOrDefault(domain,
+                        Collections.emptyList());
+            }
+        }
+        return groups;
+    }
+
     @NonNull
     public List<String> queryValidVerificationPackageNames() {
         mEnforcer.assertApprovedVerifier(mConnection.getCallingUid(), mProxy);
@@ -891,6 +964,8 @@
             }
 
             ArrayMap<String, Integer> oldStateMap = oldPkgState.getStateMap();
+            ArrayMap<String, List<UriRelativeFilterGroup>> oldGroups =
+                    oldPkgState.getUriRelativeFilterGroupMap();
             ArraySet<String> newAutoVerifyDomains =
                     mCollector.collectValidAutoVerifyDomains(newPkg);
             int newDomainsSize = newAutoVerifyDomains.size();
@@ -941,7 +1016,7 @@
 
             mAttachedPkgStates.put(pkgName, newDomainSetId, new DomainVerificationPkgState(
                     pkgName, newDomainSetId, hasAutoVerifyDomains, newStateMap, newUserStates,
-                    null /* signature */));
+                    null /* signature */, oldGroups));
         }
 
         if (sendBroadcast) {
@@ -1572,8 +1647,6 @@
     public Pair<List<ResolveInfo>, Integer> filterToApprovedApp(@NonNull Intent intent,
             @NonNull List<ResolveInfo> infos, @UserIdInt int userId,
             @NonNull Function<String, PackageStateInternal> pkgSettingFunction) {
-        String domain = intent.getData().getHost();
-
         // Collect valid infos
         ArrayMap<ResolveInfo, Integer> infoApprovals = new ArrayMap<>();
         int infosSize = infos.size();
@@ -1586,7 +1659,7 @@
         }
 
         // Find all approval levels
-        int highestApproval = fillMapWithApprovalLevels(infoApprovals, domain, userId,
+        int highestApproval = fillMapWithApprovalLevels(infoApprovals, intent.getData(), userId,
                 pkgSettingFunction);
         if (highestApproval <= APPROVAL_LEVEL_NONE) {
             return Pair.create(emptyList(), highestApproval);
@@ -1623,12 +1696,23 @@
         return Pair.create(finalList, highestApproval);
     }
 
+    private boolean matchUriRelativeFilterGroups(Uri uri, String pkgName) {
+        if (uri.getHost() == null) {
+            return false;
+        }
+        List<UriRelativeFilterGroup> groups = getUriRelativeFilterGroups(pkgName, uri.getHost());
+        if (groups.isEmpty()) {
+            return true;
+        }
+        return UriRelativeFilterGroup.matchGroupsToUri(groups, uri);
+    }
+
     /**
      * @return highest approval level found
      */
     @ApprovalLevel
     private int fillMapWithApprovalLevels(@NonNull ArrayMap<ResolveInfo, Integer> inputMap,
-            @NonNull String domain, @UserIdInt int userId,
+            @NonNull Uri uri, @UserIdInt int userId,
             @NonNull Function<String, PackageStateInternal> pkgSettingFunction) {
         int highestApproval = APPROVAL_LEVEL_NONE;
         int size = inputMap.size();
@@ -1641,12 +1725,13 @@
             ResolveInfo info = inputMap.keyAt(index);
             final String packageName = info.getComponentInfo().packageName;
             PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName);
-            if (pkgSetting == null) {
+            if (pkgSetting == null || (Flags.relativeReferenceIntentFilters()
+                    && !matchUriRelativeFilterGroups(uri, packageName))) {
                 fillInfoMapForSamePackage(inputMap, packageName, APPROVAL_LEVEL_NONE);
                 continue;
             }
-            int approval = approvalLevelForDomain(pkgSetting, domain, false, userId, DEBUG_APPROVAL,
-                    domain);
+            int approval = approvalLevelForDomain(pkgSetting, uri.getHost(), false, userId,
+                    DEBUG_APPROVAL, uri.getHost());
             highestApproval = Math.max(highestApproval, approval);
             fillInfoMapForSamePackage(inputMap, packageName, approval);
         }
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
index d71dbbb..46051fe 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.UriRelativeFilterGroup;
 import android.content.pm.Signature;
 import android.content.pm.verify.domain.DomainVerificationState;
 import android.util.ArrayMap;
@@ -26,6 +27,7 @@
 
 import com.android.internal.util.DataClass;
 
+import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
 
@@ -77,15 +79,30 @@
     @Nullable
     private final String mBackupSignatureHash;
 
+    /**
+     * List of {@link UriRelativeFilterGroup} for filtering domains.
+     */
+    @NonNull
+    private final ArrayMap<String, List<UriRelativeFilterGroup>> mUriRelativeFilterGroupMap;
+
     public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id,
             boolean hasAutoVerifyDomains) {
-        this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0), null);
+        this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0), null,
+                new ArrayMap<>());
     }
 
     public DomainVerificationPkgState(@NonNull DomainVerificationPkgState pkgState,
             @NonNull UUID id, boolean hasAutoVerifyDomains) {
         this(pkgState.getPackageName(), id, hasAutoVerifyDomains, pkgState.getStateMap(),
-                pkgState.getUserStates(), null);
+                pkgState.getUserStates(), null, new ArrayMap<>());
+    }
+
+    public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id,
+            boolean hasAutoVerifyDomains, @NonNull ArrayMap<String, Integer> stateMap,
+            @NonNull SparseArray<DomainVerificationInternalUserState> userStates,
+            @Nullable String backupSignatureHash) {
+        this(packageName, id, hasAutoVerifyDomains, stateMap, userStates, backupSignatureHash,
+                new ArrayMap<>());
     }
 
     @Nullable
@@ -158,6 +175,8 @@
      *
      *   It's assumed the domain verification agent will eventually re-verify this domain
      *   and revoke if necessary.
+     * @param uriRelativeFilterGroupMap
+     *   List of {@link UriRelativeFilterGroup} for filtering domains.
      */
     @DataClass.Generated.Member
     public DomainVerificationPkgState(
@@ -166,7 +185,8 @@
             boolean hasAutoVerifyDomains,
             @NonNull ArrayMap<String,Integer> stateMap,
             @NonNull SparseArray<DomainVerificationInternalUserState> userStates,
-            @Nullable String backupSignatureHash) {
+            @Nullable String backupSignatureHash,
+            @NonNull ArrayMap<String,List<UriRelativeFilterGroup>> uriRelativeFilterGroupMap) {
         this.mPackageName = packageName;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mPackageName);
@@ -181,6 +201,9 @@
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mUserStates);
         this.mBackupSignatureHash = backupSignatureHash;
+        this.mUriRelativeFilterGroupMap = uriRelativeFilterGroupMap;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mUriRelativeFilterGroupMap);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -239,6 +262,14 @@
         return mBackupSignatureHash;
     }
 
+    /**
+     * List of {@link UriRelativeFilterGroup} for filtering domains.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArrayMap<String,List<UriRelativeFilterGroup>> getUriRelativeFilterGroupMap() {
+        return mUriRelativeFilterGroupMap;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -251,7 +282,8 @@
                 "hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " +
                 "stateMap = " + mStateMap + ", " +
                 "userStates = " + mUserStates + ", " +
-                "backupSignatureHash = " + mBackupSignatureHash +
+                "backupSignatureHash = " + mBackupSignatureHash + ", " +
+                "uriRelativeFilterGroupMap = " + mUriRelativeFilterGroupMap +
         " }";
     }
 
@@ -273,7 +305,8 @@
                 && mHasAutoVerifyDomains == that.mHasAutoVerifyDomains
                 && Objects.equals(mStateMap, that.mStateMap)
                 && userStatesEquals(that.mUserStates)
-                && Objects.equals(mBackupSignatureHash, that.mBackupSignatureHash);
+                && Objects.equals(mBackupSignatureHash, that.mBackupSignatureHash)
+                && Objects.equals(mUriRelativeFilterGroupMap, that.mUriRelativeFilterGroupMap);
     }
 
     @Override
@@ -289,14 +322,15 @@
         _hash = 31 * _hash + Objects.hashCode(mStateMap);
         _hash = 31 * _hash + userStatesHashCode();
         _hash = 31 * _hash + Objects.hashCode(mBackupSignatureHash);
+        _hash = 31 * _hash + Objects.hashCode(mUriRelativeFilterGroupMap);
         return _hash;
     }
 
     @DataClass.Generated(
-            time = 1617315369614L,
+            time = 1707351734724L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final  boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\nprivate final @android.annotation.Nullable java.lang.String mBackupSignatureHash\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic  void removeUser(int)\npublic  void removeAllUsers()\nprivate  int userStatesHashCode()\nprivate  boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\[email protected](genToString=true, genEqualsHashCode=true)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.UUID mId\nprivate final  boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\nprivate final @android.annotation.Nullable java.lang.String mBackupSignatureHash\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.util.List<android.content.UriRelativeFilterGroup>> mUriRelativeFilterGroupMap\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic  void removeUser(int)\npublic  void removeAllUsers()\nprivate  int userStatesHashCode()\nprivate  boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\[email protected](genToString=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 25e749f..00036e4 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -137,7 +137,6 @@
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.net.module.util.NetworkCapabilitiesUtils;
 import com.android.server.power.optimization.Flags;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 
@@ -1716,14 +1715,101 @@
         return mMaxLearnedBatteryCapacityUah;
     }
 
+    public class FrameworkStatsLogger {
+        public void uidProcessStateChanged(int uid, int state) {
+            // TODO(b/155216561): It is possible for isolated uids to be in a higher
+            // state than its parent uid. We should track the highest state within the union of host
+            // and isolated uids rather than only the parent uid.
+            FrameworkStatsLog.write(FrameworkStatsLog.UID_PROCESS_STATE_CHANGED, uid,
+                    ActivityManager.processStateAmToProto(state));
+        }
+
+        public void wakelockStateChanged(int uid, WorkChain wc, String name, int type,
+                int procState, boolean acquired) {
+            int event = acquired
+                    ? FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE
+                    : FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE;
+            if (wc != null) {
+                FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(),
+                        wc.getTags(), getPowerManagerWakeLockLevel(type), name,
+                        event, procState);
+            } else {
+                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+                        mapIsolatedUid(uid), null, getPowerManagerWakeLockLevel(type), name,
+                        event, procState);
+            }
+        }
+
+        public void kernelWakeupReported(long deltaUptimeUs) {
+            FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason,
+                    /* duration_usec */ deltaUptimeUs, mLastWakeupElapsedTimeMs);
+        }
+
+        public void gpsScanStateChanged(int uid, WorkChain workChain, boolean stateOn) {
+            int event = stateOn
+                    ? FrameworkStatsLog.GPS_SCAN_STATE_CHANGED__STATE__ON
+                    : FrameworkStatsLog.GPS_SCAN_STATE_CHANGED__STATE__OFF;
+            if (workChain != null) {
+                FrameworkStatsLog.write(FrameworkStatsLog.GPS_SCAN_STATE_CHANGED,
+                        workChain.getUids(), workChain.getTags(), event);
+            } else {
+                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.GPS_SCAN_STATE_CHANGED,
+                        uid, null, event);
+            }
+        }
+
+        public void batterySaverModeChanged(boolean enabled) {
+            FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED,
+                    enabled
+                            ? FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__ON
+                            : FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__OFF);
+        }
+
+        public void deviceIdlingModeStateChanged(int mode) {
+            FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLING_MODE_STATE_CHANGED, mode);
+        }
+
+        public void deviceIdleModeStateChanged(int mode) {
+            FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mode);
+        }
+
+        public void chargingStateChanged(int status) {
+            FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status);
+        }
+
+        public void pluggedStateChanged(int plugType) {
+            FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType);
+        }
+
+        public void batteryLevelChanged(int level) {
+            FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level);
+        }
+
+        public void phoneServiceStateChanged(int state, int simState, int strengthBin) {
+            FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
+                    simState, strengthBin);
+        }
+
+        public void phoneSignalStrengthChanged(int strengthBin) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
+        }
+    }
+
+    private final FrameworkStatsLogger mFrameworkStatsLogger;
+
     @VisibleForTesting
     public BatteryStatsImpl(Clock clock, File historyDirectory, @NonNull Handler handler,
-            @NonNull PowerStatsUidResolver powerStatsUidResolver) {
+            @NonNull PowerStatsUidResolver powerStatsUidResolver,
+            @NonNull FrameworkStatsLogger frameworkStatsLogger,
+            @NonNull BatteryStatsHistory.TraceDelegate traceDelegate,
+            @NonNull BatteryStatsHistory.EventLogger eventLogger) {
         mClock = clock;
         initKernelStatsReaders();
         mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
         mHandler = handler;
         mPowerStatsUidResolver = powerStatsUidResolver;
+        mFrameworkStatsLogger = frameworkStatsLogger;
         mConstants = new Constants(mHandler);
         mStartClockTimeMs = clock.currentTimeMillis();
         mDailyFile = null;
@@ -1732,12 +1818,14 @@
             mCheckinFile = null;
             mStatsFile = null;
             mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock,
+                    traceDelegate, eventLogger);
         } else {
             mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
             mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
             mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock);
+                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock,
+                    traceDelegate, eventLogger);
         }
         mPlatformIdleStateCallback = null;
         mEnergyConsumerRetriever = null;
@@ -4269,7 +4357,7 @@
     }
 
     @GuardedBy("this")
-    private void updateBatteryPropertiesLocked() {
+    protected void updateBatteryPropertiesLocked() {
         try {
             IBatteryPropertiesRegistrar registrar = IBatteryPropertiesRegistrar.Stub.asInterface(
                     ServiceManager.getService("batteryproperties"));
@@ -4403,11 +4491,7 @@
                 return;
             }
         }
-        // TODO(b/155216561): It is possible for isolated uids to be in a higher
-        // state than its parent uid. We should track the highest state within the union of host
-        // and isolated uids rather than only the parent uid.
-        FrameworkStatsLog.write(FrameworkStatsLog.UID_PROCESS_STATE_CHANGED, uid,
-                ActivityManager.processStateAmToProto(state));
+        mFrameworkStatsLogger.uidProcessStateChanged(uid, state);
         getUidStatsLocked(parentUid, elapsedRealtimeMs, uptimeMs)
                 .updateUidProcessStateLocked(state, elapsedRealtimeMs, uptimeMs);
     }
@@ -4721,17 +4805,8 @@
             Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
             uidStats.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
 
-            int procState = uidStats.mProcessState;
-
-            if (wc != null) {
-                FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(),
-                        wc.getTags(), getPowerManagerWakeLockLevel(type), name,
-                        FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE, procState);
-            } else {
-                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
-                        mapIsolatedUid(uid), null, getPowerManagerWakeLockLevel(type), name,
-                        FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE, procState);
-            }
+            mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name, type,
+                    uidStats.mProcessState, true /* acquired */);
         }
     }
 
@@ -4774,16 +4849,8 @@
             Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
             uidStats.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
 
-            int procState = uidStats.mProcessState;
-            if (wc != null) {
-                FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(),
-                        wc.getTags(), getPowerManagerWakeLockLevel(type), name,
-                        FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE, procState);
-            } else {
-                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
-                        mapIsolatedUid(uid), null, getPowerManagerWakeLockLevel(type), name,
-                        FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE, procState);
-            }
+            mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name, type,
+                    uidStats.mProcessState, false /* acquired */);
 
             if (mappedUid != uid) {
                 // Decrement the ref count for the isolated uid and delete the mapping if uneeded.
@@ -5020,8 +5087,7 @@
             long deltaUptimeMs = uptimeMs - mLastWakeupUptimeMs;
             SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason);
             timer.add(deltaUptimeMs * 1000, 1, elapsedRealtimeMs); // time in in microseconds
-            FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason,
-                    /* duration_usec */ deltaUptimeMs * 1000, mLastWakeupElapsedTimeMs);
+            mFrameworkStatsLogger.kernelWakeupReported(deltaUptimeMs * 1000);
             mLastWakeupReason = null;
         }
     }
@@ -5159,14 +5225,7 @@
         }
         mGpsNesting++;
 
-        if (workChain == null) {
-            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.GPS_SCAN_STATE_CHANGED,
-                    mapIsolatedUid(uid), null, FrameworkStatsLog.GPS_SCAN_STATE_CHANGED__STATE__ON);
-        } else {
-            FrameworkStatsLog.write(FrameworkStatsLog.GPS_SCAN_STATE_CHANGED,
-                    workChain.getUids(), workChain.getTags(),
-                    FrameworkStatsLog.GPS_SCAN_STATE_CHANGED__STATE__ON);
-        }
+        mFrameworkStatsLogger.gpsScanStateChanged(mapIsolatedUid(uid), workChain, /* on */true);
 
         getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs).noteStartGps(elapsedRealtimeMs);
     }
@@ -5188,14 +5247,7 @@
             mGpsSignalQualityBin = -1;
         }
 
-        if (workChain == null) {
-            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.GPS_SCAN_STATE_CHANGED,
-                    mapIsolatedUid(uid), null,
-                    FrameworkStatsLog.GPS_SCAN_STATE_CHANGED__STATE__OFF);
-        } else {
-            FrameworkStatsLog.write(FrameworkStatsLog.GPS_SCAN_STATE_CHANGED, workChain.getUids(),
-                    workChain.getTags(), FrameworkStatsLog.GPS_SCAN_STATE_CHANGED__STATE__OFF);
-        }
+        mFrameworkStatsLogger.gpsScanStateChanged(mapIsolatedUid(uid), workChain, /* on */ false);
 
         getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs).noteStopGps(elapsedRealtimeMs);
     }
@@ -5673,10 +5725,7 @@
         } else {
             // Log an initial value for BATTERY_SAVER_MODE_STATE_CHANGED in order to
             // allow the atom to read all future state changes.
-            FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED,
-                    enabled
-                        ? FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__ON
-                        : FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__OFF);
+            mFrameworkStatsLogger.batterySaverModeChanged(enabled);
         }
     }
 
@@ -5696,10 +5745,7 @@
                         HistoryItem.STATE2_POWER_SAVE_FLAG);
                 mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtimeMs);
             }
-            FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED,
-                    enabled
-                        ? FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__ON
-                        : FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__OFF);
+            mFrameworkStatsLogger.batterySaverModeChanged(enabled);
         }
     }
 
@@ -5727,7 +5773,7 @@
             if (nowIdling)           statsmode = DEVICE_IDLE_MODE_DEEP;
             else if (nowLightIdling) statsmode = DEVICE_IDLE_MODE_LIGHT;
             else                     statsmode = DEVICE_IDLE_MODE_OFF;
-            FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLING_MODE_STATE_CHANGED, statsmode);
+            mFrameworkStatsLogger.deviceIdlingModeStateChanged(statsmode);
         }
         if (mDeviceIdling != nowIdling) {
             mDeviceIdling = nowIdling;
@@ -5769,7 +5815,7 @@
                 mDeviceIdleModeFullTimer.startRunningLocked(elapsedRealtimeMs);
             }
             mDeviceIdleMode = mode;
-            FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mode);
+            mFrameworkStatsLogger.deviceIdleModeStateChanged(mode);
         }
     }
 
@@ -5933,8 +5979,7 @@
                 addStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG;
                 newHistory = true;
                 mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtimeMs);
-                FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
-                        simState, strengthBin);
+                mFrameworkStatsLogger.phoneServiceStateChanged(state, simState, strengthBin);
             }
         }
 
@@ -5944,8 +5989,7 @@
                 removeStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG;
                 newHistory = true;
                 mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtimeMs);
-                FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
-                        simState, strengthBin);
+                mFrameworkStatsLogger.phoneServiceStateChanged(state, simState, strengthBin);
             }
         }
 
@@ -5966,8 +6010,7 @@
                 }
                 newSignalStrength = strengthBin;
                 newHistory = true;
-                FrameworkStatsLog.write(
-                        FrameworkStatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
+                mFrameworkStatsLogger.phoneSignalStrengthChanged(strengthBin);
             } else {
                 stopAllPhoneSignalStrengthTimersLocked(-1, elapsedRealtimeMs);
             }
@@ -6076,7 +6119,7 @@
         // Unknown is included in DATA_CONNECTION_OTHER.
         int bin = DATA_CONNECTION_OUT_OF_SERVICE;
         if (hasData) {
-            if (dataType > 0 && dataType <= TelephonyManager.getAllNetworkTypes().length) {
+            if (dataType > 0 && dataType <= NUM_ALL_NETWORK_TYPES) {
                 bin = dataType;
             } else {
                 switch (serviceType) {
@@ -6995,7 +7038,7 @@
     /** @hide */
     public void noteNetworkInterfaceForTransports(String iface, int[] transportTypes) {
         if (TextUtils.isEmpty(iface)) return;
-        final int displayTransport = NetworkCapabilitiesUtils.getDisplayTransport(transportTypes);
+        final int displayTransport = getDisplayTransport(transportTypes);
 
         synchronized (mModemNetworkLock) {
             if (displayTransport == TRANSPORT_CELLULAR) {
@@ -10507,8 +10550,7 @@
                 long elapsedRealtimeMs, long uptimeMs) {
             int uidRunningState;
             // Make special note of Foreground Services
-            final boolean userAwareService =
-                    (ActivityManager.isForegroundService(procState));
+            final boolean userAwareService = ActivityManager.isForegroundService(procState);
             uidRunningState = BatteryStats.mapToInternalProcessState(procState);
 
             if (mProcessState == uidRunningState && userAwareService == mInForegroundService) {
@@ -10912,6 +10954,7 @@
         mPowerProfile = powerProfile;
         mCpuScalingPolicies = cpuScalingPolicies;
         mPowerStatsUidResolver = powerStatsUidResolver;
+        mFrameworkStatsLogger = new FrameworkStatsLogger();
 
         initPowerProfile();
 
@@ -10966,7 +11009,7 @@
 
         // Notify statsd that the system is initially not in doze.
         mDeviceIdleMode = DEVICE_IDLE_MODE_OFF;
-        FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
+        mFrameworkStatsLogger.deviceIdleModeStateChanged(mDeviceIdleMode);
     }
 
     private void recordPowerStats(PowerStats stats) {
@@ -11702,7 +11745,9 @@
             mWakeupReasonStats.clear();
         }
 
-        mTmpRailStats.reset();
+        if (mTmpRailStats != null) {
+            mTmpRailStats.reset();
+        }
 
         EnergyConsumerStats.resetIfNotNull(mGlobalEnergyConsumerStats);
 
@@ -11864,6 +11909,78 @@
         return networkStatsManager.getWifiUidStats();
     }
 
+    private static class NetworkStatsDelta {
+        int mUid;
+        int mSet;
+        long mRxBytes;
+        long mRxPackets;
+        long mTxBytes;
+        long mTxPackets;
+
+        public int getUid() {
+            return mUid;
+        }
+
+
+        public int getSet() {
+            return mSet;
+        }
+
+        public long getRxBytes() {
+            return mRxBytes;
+        }
+
+        public long getRxPackets() {
+            return mRxPackets;
+        }
+
+        public long getTxBytes() {
+            return mTxBytes;
+        }
+
+        public long getTxPackets() {
+            return mTxPackets;
+        }
+    }
+
+    private List<NetworkStatsDelta> computeDelta(NetworkStats currentStats,
+            NetworkStats lastStats) {
+        List<NetworkStatsDelta> deltaList = new ArrayList<>();
+        for (NetworkStats.Entry entry : currentStats) {
+            NetworkStatsDelta delta = new NetworkStatsDelta();
+            delta.mUid = entry.getUid();
+            delta.mSet = entry.getSet();
+            NetworkStats.Entry lastEntry = null;
+            if (lastStats != null) {
+                for (NetworkStats.Entry e : lastStats) {
+                    if (e.getUid() == entry.getUid() && e.getSet() == entry.getSet()
+                            && e.getTag() == entry.getTag()
+                            && e.getMetered() == entry.getMetered()
+                            && e.getRoaming() == entry.getRoaming()
+                            && e.getDefaultNetwork() == entry.getDefaultNetwork()
+                            /*&& Objects.equals(e.getIface(), entry.getIface())*/) {
+                        lastEntry = e;
+                        break;
+                    }
+                }
+            }
+            if (lastEntry != null) {
+                delta.mRxBytes = entry.getRxBytes() - lastEntry.getRxBytes();
+                delta.mRxPackets = entry.getRxPackets() - lastEntry.getRxPackets();
+                delta.mTxBytes = entry.getTxBytes() - lastEntry.getTxBytes();
+                delta.mTxPackets = entry.getTxPackets() - lastEntry.getTxPackets();
+            } else {
+                delta.mRxBytes = entry.getRxBytes();
+                delta.mRxPackets = entry.getRxPackets();
+                delta.mTxBytes = entry.getTxBytes();
+                delta.mTxPackets = entry.getTxPackets();
+            }
+            deltaList.add(delta);
+        }
+
+        return deltaList;
+    }
+
     /**
      * Distribute WiFi energy info and network traffic to apps.
      * @param info The energy information from the WiFi controller.
@@ -11879,14 +11996,14 @@
         }
 
         // Grab a separate lock to acquire the network stats, which may do I/O.
-        NetworkStats delta = null;
+        List<NetworkStatsDelta> delta;
         synchronized (mWifiNetworkLock) {
             final NetworkStats latestStats = readWifiNetworkStatsLocked(networkStatsManager);
             if (latestStats != null) {
-                delta = mLastWifiNetworkStats != null
-                        ? latestStats.subtract(mLastWifiNetworkStats)
-                        : latestStats.subtract(new NetworkStats(0, -1));
+                delta = computeDelta(latestStats, mLastWifiNetworkStats);
                 mLastWifiNetworkStats = latestStats;
+            } else {
+                delta = null;
             }
         }
 
@@ -11914,7 +12031,7 @@
             long totalTxPackets = 0;
             long totalRxPackets = 0;
             if (delta != null) {
-                for (NetworkStats.Entry entry : delta) {
+                for (NetworkStatsDelta entry : delta) {
                     if (DEBUG_ENERGY) {
                         Slog.d(TAG, "Wifi uid " + entry.getUid()
                                 + ": delta rx=" + entry.getRxBytes()
@@ -12199,13 +12316,16 @@
                 }
                 // Converting uWs to mAms.
                 // Conversion: (uWs * (1000ms / 1s) * (1mW / 1000uW)) / mV = mAms
-                long monitoredRailChargeConsumedMaMs =
-                        (long) (mTmpRailStats.getWifiTotalEnergyUseduWs() / opVolt);
+                long monitoredRailChargeConsumedMaMs = mTmpRailStats != null
+                        ? (long) (mTmpRailStats.getWifiTotalEnergyUseduWs() / opVolt)
+                        : 0L;
                 mWifiActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked(
                         monitoredRailChargeConsumedMaMs);
                 mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs,
                         (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR));
-                mTmpRailStats.resetWifiTotalEnergyUsed();
+                if (mTmpRailStats != null) {
+                    mTmpRailStats.resetWifiTotalEnergyUsed();
+                }
 
                 if (uidEstimatedConsumptionMah != null) {
                     totalEstimatedConsumptionMah = Math.max(controllerMaMs / MILLISECONDS_IN_HOUR,
@@ -13519,14 +13639,16 @@
             }
         }
 
-        // Record whether we've seen a non-zero time (for debugging b/22716723).
-        if (wakelockStats.isEmpty()) {
-            Slog.wtf(TAG, "All kernel wakelocks had time of zero");
-        }
+        if (DEBUG) {
+            // Record whether we've seen a non-zero time (for debugging b/22716723).
+            if (wakelockStats.isEmpty()) {
+                Slog.wtf(TAG, "All kernel wakelocks had time of zero");
+            }
 
-        if (numWakelocksSetStale == mKernelWakelockStats.size()) {
-            Slog.wtf(TAG, "All kernel wakelocks were set stale. new version=" +
-                    wakelockStats.kernelWakelockVersion);
+            if (numWakelocksSetStale == mKernelWakelockStats.size()) {
+                Slog.wtf(TAG, "All kernel wakelocks were set stale. new version="
+                        + wakelockStats.kernelWakelockVersion);
+            }
         }
     }
 
@@ -14711,13 +14833,13 @@
     // Inform StatsLog of setBatteryState changes.
     private void reportChangesToStatsLog(final int status, final int plugType, final int level) {
         if (!mHaveBatteryLevel || mBatteryStatus != status) {
-            FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status);
+            mFrameworkStatsLogger.chargingStateChanged(status);
         }
         if (!mHaveBatteryLevel || mBatteryPlugType != plugType) {
-            FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType);
+            mFrameworkStatsLogger.pluggedStateChanged(plugType);
         }
         if (!mHaveBatteryLevel || mBatteryLevel != level) {
-            FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level);
+            mFrameworkStatsLogger.batteryLevelChanged(level);
         }
     }
 
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index aba8e5f..1050e8a 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -32,6 +32,7 @@
 
 import java.util.ArrayList;
 
[email protected]
 public class MobileRadioPowerCalculator extends PowerCalculator {
     private static final String TAG = "MobRadioPowerCalculator";
     private static final boolean DEBUG = PowerCalculator.DEBUG;
@@ -320,7 +321,7 @@
 
     private double calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs) {
         final long elapsedRealtimeMs = elapsedRealtimeUs / 1000;
-        final int txLvlCount = CellSignalStrength.getNumSignalStrengthLevels();
+        final int txLvlCount = NUM_SIGNAL_STRENGTH_LEVELS;
         double consumptionMah = 0.0;
 
         if (DEBUG) {
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
index 88d3daf..62a637e 100644
--- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -19,6 +19,8 @@
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
 import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
 
+import android.app.wearable.Flags;
+import android.app.wearable.WearableSensingManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -30,6 +32,7 @@
 import android.service.wearable.WearableSensingService;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.infra.ServiceConnector;
 
 import java.io.IOException;
@@ -40,6 +43,17 @@
             com.android.server.wearable.RemoteWearableSensingService.class.getSimpleName();
     private final static boolean DEBUG = false;
 
+    private final Object mSecureWearableConnectionLock = new Object();
+
+    // mNextSecureWearableConnectionContext will only be non-null when we are waiting for the
+    // WearableSensingService process to restart. It will be set to null after it is passed into
+    // WearableSensingService.
+    @GuardedBy("mSecureWearableConnectionLock")
+    private SecureWearableConnectionContext mNextSecureWearableConnectionContext;
+
+    @GuardedBy("mSecureWearableConnectionLock")
+    private boolean mSecureWearableConnectionProvided = false;
+
     RemoteWearableSensingService(Context context, ComponentName serviceName,
             int userId) {
         super(context, new Intent(
@@ -66,18 +80,84 @@
     public void provideSecureWearableConnection(
             ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
         if (DEBUG) {
-            Slog.i(TAG, "Providing secure wearable connection.");
+            Slog.i(TAG, "#provideSecureWearableConnection");
         }
-        var unused = post(
-                service -> {
-                    service.provideSecureWearableConnection(secureWearableConnection, callback);
-                    try {
-                        // close the local fd after it has been sent to the WSS process
-                        secureWearableConnection.close();
-                    } catch (IOException ex) {
-                        Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
-                    }
-                });
+        if (!Flags.enableRestartWssProcess()) {
+            Slog.d(
+                    TAG,
+                    "FLAG_ENABLE_RESTART_WSS_PROCESS is disabled. Do not attempt to restart the"
+                        + " WearableSensingService process");
+            provideSecureWearableConnectionInternal(secureWearableConnection, callback);
+            return;
+        }
+        synchronized (mSecureWearableConnectionLock) {
+            if (mNextSecureWearableConnectionContext != null) {
+                // A process restart is in progress, #binderDied is about to be called. Replace
+                // the previous mNextSecureWearableConnectionContext with the current one
+                Slog.i(
+                        TAG,
+                        "A new wearable connection is provided before the process restart triggered"
+                            + " by the previous connection is complete. Discarding the previous"
+                            + " connection.");
+                if (Flags.enableProvideWearableConnectionApi()) {
+                    WearableSensingManagerPerUserService.notifyStatusCallback(
+                            mNextSecureWearableConnectionContext.mStatusCallback,
+                            WearableSensingManager.STATUS_CHANNEL_ERROR);
+                }
+                mNextSecureWearableConnectionContext =
+                        new SecureWearableConnectionContext(secureWearableConnection, callback);
+                return;
+            }
+            if (!mSecureWearableConnectionProvided) {
+                // no need to kill the process
+                provideSecureWearableConnectionInternal(secureWearableConnection, callback);
+                mSecureWearableConnectionProvided = true;
+                return;
+            }
+            mNextSecureWearableConnectionContext =
+                    new SecureWearableConnectionContext(secureWearableConnection, callback);
+            // Killing the process causes the binder to die. #binderDied will then be triggered
+            killWearableSensingServiceProcess();
+        }
+    }
+
+    private void provideSecureWearableConnectionInternal(
+            ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+        Slog.d(TAG, "Providing secure wearable connection.");
+        var unused =
+                post(
+                        service -> {
+                            service.provideSecureWearableConnection(
+                                    secureWearableConnection, callback);
+                            try {
+                                // close the local fd after it has been sent to the WSS process
+                                secureWearableConnection.close();
+                            } catch (IOException ex) {
+                                Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+                            }
+                        });
+    }
+
+    @Override
+    public void binderDied() {
+        super.binderDied();
+        synchronized (mSecureWearableConnectionLock) {
+            if (mNextSecureWearableConnectionContext != null) {
+                // This will call #post, which will recreate the process and bind to it
+                provideSecureWearableConnectionInternal(
+                        mNextSecureWearableConnectionContext.mSecureWearableConnection,
+                        mNextSecureWearableConnectionContext.mStatusCallback);
+                mNextSecureWearableConnectionContext = null;
+            } else {
+                mSecureWearableConnectionProvided = false;
+                Slog.w(TAG, "Binder died but there is no secure wearable connection to provide.");
+            }
+        }
+    }
+
+    /** Kills the WearableSensingService process. */
+    public void killWearableSensingServiceProcess() {
+        var unused = post(service -> service.killProcess());
     }
 
     /**
@@ -176,4 +256,15 @@
                                         packageName,
                                         statusCallback));
     }
+
+    private static class SecureWearableConnectionContext {
+        final ParcelFileDescriptor mSecureWearableConnection;
+        final RemoteCallback mStatusCallback;
+
+        SecureWearableConnectionContext(
+                ParcelFileDescriptor secureWearableConnection, RemoteCallback statusCallback) {
+            this.mSecureWearableConnection = secureWearableConnection;
+            this.mStatusCallback = statusCallback;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index 0e8b82f..9ba4433 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -44,6 +44,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Per-user manager service for managing sensing {@link AmbientContextEvent}s on Wearables.
@@ -68,7 +69,7 @@
         super(master, lock, userId);
     }
 
-    static void notifyStatusCallback(RemoteCallback statusCallback, int statusCode) {
+    public static void notifyStatusCallback(RemoteCallback statusCallback, int statusCode) {
         Bundle bundle = new Bundle();
         bundle.putInt(
                 WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY, statusCode);
@@ -183,11 +184,11 @@
         }
         synchronized (mSecureChannelLock) {
             if (mSecureChannel != null) {
-                // TODO(b/321012559): Kill the WearableSensingService process if it has not been
-                // killed from onError
                 mSecureChannel.close();
             }
             try {
+                final AtomicReference<WearableSensingSecureChannel> currentSecureChannelRef =
+                        new AtomicReference<>();
                 mSecureChannel =
                         WearableSensingSecureChannel.create(
                                 getContext().getSystemService(CompanionDeviceManager.class),
@@ -206,8 +207,17 @@
 
                                     @Override
                                     public void onError() {
-                                        // TODO(b/321012559): Kill the WearableSensingService
-                                        // process if mSecureChannel has not been reassigned
+                                        if (Flags.enableRestartWssProcess()) {
+                                            synchronized (mSecureChannelLock) {
+                                                if (mSecureChannel != null
+                                                        && mSecureChannel
+                                                                == currentSecureChannelRef.get()) {
+                                                    mRemoteService
+                                                            .killWearableSensingServiceProcess();
+                                                    mSecureChannel = null;
+                                                }
+                                            }
+                                        }
                                         if (Flags.enableProvideWearableConnectionApi()) {
                                             notifyStatusCallback(
                                                     callback,
@@ -215,6 +225,7 @@
                                         }
                                     }
                                 });
+                currentSecureChannelRef.set(mSecureChannel);
             } catch (IOException ex) {
                 Slog.e(TAG, "Unable to create the secure channel.", ex);
                 if (Flags.enableProvideWearableConnectionApi()) {
diff --git a/services/core/java/com/android/server/wm/ActivityCallerState.java b/services/core/java/com/android/server/wm/ActivityCallerState.java
new file mode 100644
index 0000000..4416605
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityCallerState.java
@@ -0,0 +1,245 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.content.ClipData;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.uri.GrantUri;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.WeakHashMap;
+
+/**
+ * Represents the state of activity callers. Used by {@link ActivityRecord}.
+ * @hide
+ */
+final class ActivityCallerState {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityCallerState" : TAG_ATM;
+
+    // XML tags for CallerInfo
+    private static final String TAG_READABLE_CONTENT_URI = "readable_content_uri";
+    private static final String TAG_WRITABLE_CONTENT_URI = "writable_content_uri";
+    private static final String TAG_INACCESSIBLE_CONTENT_URI = "inaccessible_content_uri";
+    private static final String ATTR_SOURCE_USER_ID = "source_user_id";
+    private static final String ATTR_URI = "uri";
+    private static final String ATTR_PREFIX = "prefix";
+
+    // Map for storing CallerInfo instances
+    private final WeakHashMap<IBinder, CallerInfo> mCallerTokenInfoMap = new WeakHashMap<>();
+
+    final ActivityTaskManagerService mAtmService;
+
+    ActivityCallerState(ActivityTaskManagerService service) {
+        mAtmService = service;
+    }
+
+    CallerInfo getCallerInfoOrNull(IBinder callerToken) {
+        return mCallerTokenInfoMap.getOrDefault(callerToken, null);
+    }
+
+    void add(IBinder callerToken, CallerInfo callerInfo) {
+        mCallerTokenInfoMap.put(callerToken, callerInfo);
+    }
+
+    void computeCallerInfo(IBinder callerToken, Intent intent, int callerUid) {
+        final CallerInfo callerInfo = new CallerInfo();
+        mCallerTokenInfoMap.put(callerToken, callerInfo);
+
+        final ArraySet<Uri> contentUris = getContentUrisFromIntent(intent);
+        for (int i = contentUris.size() - 1; i >= 0; i--) {
+            final Uri contentUri = contentUris.valueAt(i);
+
+            final boolean hasRead = addContentUriIfUidHasPermission(contentUri, callerUid,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION, callerInfo.mReadableContentUris);
+
+            final boolean hasWrite = addContentUriIfUidHasPermission(contentUri, callerUid,
+                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION, callerInfo.mWritableContentUris);
+
+            if (!hasRead && !hasWrite) {
+                callerInfo.mInaccessibleContentUris.add(convertToGrantUri(contentUri,
+                        /* modeFlags */ 0));
+            }
+        }
+    }
+
+    boolean checkContentUriPermission(IBinder callerToken, GrantUri grantUri, int modeFlags) {
+        if (!Intent.isAccessUriMode(modeFlags)) {
+            throw new IllegalArgumentException("Mode flags are not access URI mode flags: "
+                    + modeFlags);
+        }
+
+        final CallerInfo callerInfo = mCallerTokenInfoMap.getOrDefault(callerToken, null);
+        if (callerInfo == null) {
+            Slog.e(TAG, "Caller not found for checkContentUriPermission of: "
+                    + grantUri.uri.toSafeString());
+            return false;
+        }
+
+        if (callerInfo.mInaccessibleContentUris.contains(grantUri)) {
+            return false;
+        }
+
+        final boolean readMet = callerInfo.mReadableContentUris.contains(grantUri);
+        final boolean writeMet = callerInfo.mWritableContentUris.contains(grantUri);
+
+        if (!readMet && !writeMet) {
+            throw new IllegalArgumentException("The supplied URI wasn't passed at launch: "
+                    + grantUri.uri.toSafeString());
+        }
+
+        final boolean checkRead = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0;
+        if (checkRead && !readMet) {
+            return false;
+        }
+
+        final boolean checkWrite = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0;
+        if (checkWrite && !writeMet) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean addContentUriIfUidHasPermission(Uri contentUri, int uid, int modeFlags,
+            ArraySet<GrantUri> grantUris) {
+        final GrantUri grantUri = convertToGrantUri(contentUri, modeFlags);
+        if (mAtmService.mUgmInternal.checkUriPermission(grantUri, uid,
+                modeFlags, /* isFullAccessForContentUri */ true)) {
+            grantUris.add(grantUri);
+            return true;
+        }
+        return false;
+    }
+
+    private static GrantUri convertToGrantUri(Uri contentUri, int modeFlags) {
+        return new GrantUri(ContentProvider.getUserIdFromUri(contentUri,
+                UserHandle.getCallingUserId()), ContentProvider.getUriWithoutUserId(contentUri),
+                modeFlags);
+    }
+
+    private static ArraySet<Uri> getContentUrisFromIntent(Intent intent) {
+        final ArraySet<Uri> uris = new ArraySet<>();
+        if (intent == null) return uris;
+
+        // getData
+        addUriIfContentUri(intent.getData(), uris);
+
+        final ClipData clipData = intent.getClipData();
+        if (clipData == null) return uris;
+
+        for (int i = 0; i < clipData.getItemCount(); i++) {
+            final ClipData.Item item = clipData.getItemAt(i);
+
+            // getUri
+            addUriIfContentUri(item.getUri(), uris);
+
+            // getIntent
+            uris.addAll(getContentUrisFromIntent(item.getIntent()));
+        }
+        return uris;
+    }
+
+    private static void addUriIfContentUri(Uri uri, ArraySet<Uri> uris) {
+        if (uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+            uris.add(uri);
+        }
+    }
+
+    public static final class CallerInfo {
+        final ArraySet<GrantUri> mReadableContentUris = new ArraySet<>();
+        final ArraySet<GrantUri> mWritableContentUris = new ArraySet<>();
+        final ArraySet<GrantUri> mInaccessibleContentUris = new ArraySet<>();
+
+        public void saveToXml(TypedXmlSerializer out)
+                throws IOException, XmlPullParserException {
+            for (int i = mReadableContentUris.size() - 1; i >= 0; i--) {
+                saveGrantUriToXml(out, mReadableContentUris.valueAt(i), TAG_READABLE_CONTENT_URI);
+            }
+
+            for (int i = mWritableContentUris.size() - 1; i >= 0; i--) {
+                saveGrantUriToXml(out, mWritableContentUris.valueAt(i), TAG_WRITABLE_CONTENT_URI);
+            }
+
+            for (int i = mInaccessibleContentUris.size() - 1; i >= 0; i--) {
+                saveGrantUriToXml(out, mInaccessibleContentUris.valueAt(i),
+                        TAG_INACCESSIBLE_CONTENT_URI);
+            }
+        }
+
+        public static CallerInfo restoreFromXml(TypedXmlPullParser in)
+                throws IOException, XmlPullParserException {
+            CallerInfo callerInfo = new CallerInfo();
+            final int outerDepth = in.getDepth();
+            int event;
+            while (((event = in.next()) != END_DOCUMENT)
+                    && (event != END_TAG || in.getDepth() >= outerDepth)) {
+                if (event == START_TAG) {
+                    final String name = in.getName();
+                    if (TAG_READABLE_CONTENT_URI.equals(name)) {
+                        callerInfo.mReadableContentUris.add(restoreGrantUriFromXml(in));
+                    } else if (TAG_WRITABLE_CONTENT_URI.equals(name)) {
+                        callerInfo.mWritableContentUris.add(restoreGrantUriFromXml(in));
+                    } else if (TAG_INACCESSIBLE_CONTENT_URI.equals(name)) {
+                        callerInfo.mInaccessibleContentUris.add(restoreGrantUriFromXml(in));
+                    } else {
+                        Slog.w(TAG, "restoreActivity: unexpected name=" + name);
+                        XmlUtils.skipCurrentTag(in);
+                    }
+                }
+            }
+            return callerInfo;
+        }
+
+        private void saveGrantUriToXml(TypedXmlSerializer out, GrantUri grantUri, String tag)
+                throws IOException, XmlPullParserException {
+            out.startTag(null, tag);
+            out.attributeInt(null, ATTR_SOURCE_USER_ID, grantUri.sourceUserId);
+            out.attribute(null, ATTR_URI, String.valueOf(grantUri.uri));
+            out.attributeBoolean(null, ATTR_PREFIX, grantUri.prefix);
+            out.endTag(null, tag);
+        }
+
+        private static GrantUri restoreGrantUriFromXml(TypedXmlPullParser in)
+                throws IOException, XmlPullParserException {
+            int sourceUserId = in.getAttributeInt(null, ATTR_SOURCE_USER_ID, 0);
+            Uri uri = Uri.parse(in.getAttributeValue(null, ATTR_URI));
+            boolean prefix = in.getAttributeBoolean(null, ATTR_PREFIX, false);
+            return new GrantUri(sourceUserId, uri,
+                    prefix ? Intent.FLAG_GRANT_PREFIX_URI_PERMISSION : 0);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 2e0546e..173e139 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -30,6 +30,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -80,6 +82,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManagerInternal;
 import android.content.res.Configuration;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -103,6 +106,7 @@
 import com.android.server.Watchdog;
 import com.android.server.pm.KnownPackages;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.uri.GrantUri;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.vr.VrManagerInternal;
 
@@ -715,6 +719,32 @@
         return null;
     }
 
+    /**
+     * @param uri This uri must NOT contain an embedded userId.
+     * @param userId The userId in which the uri is to be resolved.
+     */
+    @Override
+    public int checkActivityCallerContentUriPermission(IBinder activityToken, IBinder callerToken,
+            Uri uri, int modeFlags, int userId) {
+        // 1. Check if we have access to the URI - > throw if we don't
+        GrantUri grantUri = new GrantUri(userId, uri, modeFlags);
+        if (!mService.mUgmInternal.checkUriPermission(grantUri, Binder.getCallingUid(), modeFlags,
+                /* isFullAccessForContentUri */ true)) {
+            throw new SecurityException("You don't have access to the content URI, hence can't"
+                    + " check if the caller has access to it: " + uri);
+        }
+
+        // 2. Get the permission result for the caller
+        synchronized (mGlobalLock) {
+            final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken);
+            if (r != null) {
+                boolean granted = r.checkContentUriPermission(callerToken, grantUri, modeFlags);
+                return granted ? PERMISSION_GRANTED : PERMISSION_DENIED;
+            }
+        }
+        return PERMISSION_DENIED;
+    }
+
     /** Whether the call to one of the getLaunchedFrom APIs is performed by an internal caller. */
     private boolean isInternalCallerGetLaunchedFrom(int uid) {
         if (UserHandle.getAppId(uid) == SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c2117ea..09c329b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -381,6 +381,7 @@
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.uri.GrantUri;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriPermissionOwner;
 import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
@@ -436,6 +437,7 @@
     private static final String ATTR_LAUNCHEDFROMFEATURE = "launched_from_feature";
     private static final String ATTR_RESOLVEDTYPE = "resolved_type";
     private static final String ATTR_COMPONENTSPECIFIED = "component_specified";
+    private static final String TAG_INITIAL_CALLER_INFO = "initial_caller_info";
     static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_";
 
     // How many activities have to be scheduled to stop to force a stop pass.
@@ -472,6 +474,7 @@
     private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f;
 
     final ActivityTaskManagerService mAtmService;
+    final ActivityCallerState mCallerState;
     @NonNull
     final ActivityInfo info; // activity info provided by developer in AndroidManifest
     // Which user is this running for?
@@ -2021,6 +2024,18 @@
         }
     }
 
+    void computeInitialCallerInfo() {
+        computeCallerInfo(initialCallerInfoAccessToken, intent, launchedFromUid);
+    }
+
+    void computeCallerInfo(IBinder callerToken, Intent intent, int callerUid) {
+        mCallerState.computeCallerInfo(callerToken, intent, callerUid);
+    }
+
+    boolean checkContentUriPermission(IBinder callerToken, GrantUri grantUri, int modeFlags) {
+        return mCallerState.checkContentUriPermission(callerToken, grantUri, modeFlags);
+    }
+
     private ActivityRecord(ActivityTaskManagerService _service, WindowProcessController _caller,
             int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage,
             @Nullable String _launchedFromFeature, Intent _intent, String _resolvedType,
@@ -2246,6 +2261,7 @@
                             }
                             return appContext;
                         });
+        mCallerState = new ActivityCallerState(mAtmService);
     }
 
     /**
@@ -10113,6 +10129,16 @@
             mPersistentState.saveToXml(out);
             out.endTag(null, TAG_PERSISTABLEBUNDLE);
         }
+
+        if (android.security.Flags.contentUriPermissionApis()) {
+            ActivityCallerState.CallerInfo initialCallerInfo = mCallerState.getCallerInfoOrNull(
+                    initialCallerInfoAccessToken);
+            if (initialCallerInfo != null) {
+                out.startTag(null, TAG_INITIAL_CALLER_INFO);
+                initialCallerInfo.saveToXml(out);
+                out.endTag(null, TAG_INITIAL_CALLER_INFO);
+            }
+        }
     }
 
     static ActivityRecord restoreFromXml(TypedXmlPullParser in,
@@ -10127,6 +10153,7 @@
         int userId = in.getAttributeInt(null, ATTR_USERID, 0);
         long createTime = in.getAttributeLong(null, ATTR_ID, -1);
         final int outerDepth = in.getDepth();
+        ActivityCallerState.CallerInfo initialCallerInfo = null;
 
         TaskDescription taskDescription = new TaskDescription();
         taskDescription.restoreFromXml(in);
@@ -10146,6 +10173,9 @@
                     persistentState = PersistableBundle.restoreFromXml(in);
                     if (DEBUG) Slog.d(TaskPersister.TAG,
                             "ActivityRecord: persistentState=" + persistentState);
+                } else if (android.security.Flags.contentUriPermissionApis()
+                        && TAG_INITIAL_CALLER_INFO.equals(name)) {
+                    initialCallerInfo = ActivityCallerState.CallerInfo.restoreFromXml(in);
                 } else {
                     Slog.w(TAG, "restoreActivity: unexpected name=" + name);
                     XmlUtils.skipCurrentTag(in);
@@ -10164,7 +10194,7 @@
             throw new XmlPullParserException("restoreActivity resolver error. Intent=" + intent +
                     " resolvedType=" + resolvedType);
         }
-        return new ActivityRecord.Builder(service)
+        final ActivityRecord r = new ActivityRecord.Builder(service)
                 .setLaunchedFromUid(launchedFromUid)
                 .setLaunchedFromPackage(launchedFromPackage)
                 .setLaunchedFromFeature(launchedFromFeature)
@@ -10176,6 +10206,11 @@
                 .setTaskDescription(taskDescription)
                 .setCreateTime(createTime)
                 .build();
+
+        if (android.security.Flags.contentUriPermissionApis() && initialCallerInfo != null) {
+            r.mCallerState.add(r.initialCallerInfoAccessToken, initialCallerInfo);
+        }
+        return r;
     }
 
     private static boolean isInVrUiMode(Configuration config) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d99000e..07afa5f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1586,6 +1586,10 @@
             return null;
         }
 
+        if (android.security.Flags.contentUriPermissionApis() && started.isAttached()) {
+            started.computeInitialCallerInfo();
+        }
+
         // Apply setAlwaysOnTop when starting an activity is successful regardless of creating
         // a new Activity or reusing the existing activity.
         if (options != null && options.getTaskAlwaysOnTop()) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8773366..640c9dc 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1503,7 +1503,8 @@
         a.configChanges = 0xffffffff;
 
         if (homePanelDream()) {
-            a.launchMode = ActivityInfo.LAUNCH_SINGLE_TASK;
+            a.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+            a.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
         } else {
             a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
             a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE;
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index b7eab08..e6ef90b 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -32,6 +32,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
+import android.window.InputTransferToken;
 
 /**
  * Keeps track of embedded windows.
@@ -44,7 +45,7 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "EmbeddedWindowController" : TAG_WM;
     /* maps input token to an embedded window */
     private ArrayMap<IBinder /*input token */, EmbeddedWindow> mWindows = new ArrayMap<>();
-    private ArrayMap<IBinder /*input transfer token */, EmbeddedWindow>
+    private ArrayMap<InputTransferToken /*input transfer token */, EmbeddedWindow>
             mWindowsByInputTransferToken = new ArrayMap<>();
     private ArrayMap<IBinder /*window token*/, EmbeddedWindow> mWindowsByWindowToken =
         new ArrayMap<>();
@@ -66,7 +67,7 @@
     void add(IBinder inputToken, EmbeddedWindow window) {
         try {
             mWindows.put(inputToken, window);
-            final IBinder inputTransferToken = window.getInputTransferToken();
+            final InputTransferToken inputTransferToken = window.getInputTransferToken();
             mWindowsByInputTransferToken.put(inputTransferToken, window);
             mWindowsByWindowToken.put(window.getWindowToken(), window);
             updateProcessController(window);
@@ -126,7 +127,7 @@
         return mWindows.get(inputToken);
     }
 
-    EmbeddedWindow getByInputTransferToken(IBinder inputTransferToken) {
+    EmbeddedWindow getByInputTransferToken(InputTransferToken inputTransferToken) {
         return mWindowsByInputTransferToken.get(inputTransferToken);
     }
 
@@ -152,7 +153,7 @@
          * to request focus transfer and gesture transfer to the embedded. This is not the input
          * token since we don't want to give clients access to each others input token.
          */
-        private final IBinder mInputTransferToken;
+        private final InputTransferToken mInputTransferToken;
 
         private boolean mIsFocusable;
 
@@ -170,7 +171,7 @@
          */
         EmbeddedWindow(Session session, WindowManagerService service, IBinder clientToken,
                        WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
-                       int displayId, IBinder inputTransferToken, String inputHandleName,
+                       int displayId, InputTransferToken inputTransferToken, String inputHandleName,
                        boolean isFocusable) {
             mSession = session;
             mWmService = service;
@@ -254,7 +255,7 @@
             return mOwnerUid;
         }
 
-        IBinder getInputTransferToken() {
+        InputTransferToken getInputTransferToken() {
             return mInputTransferToken;
         }
 
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 95e6ca6..3c8c55e 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -80,6 +80,7 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager;
 import android.window.ClientWindowFrames;
+import android.window.InputTransferToken;
 import android.window.OnBackInvokedCallbackInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -914,10 +915,11 @@
 
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface,
-            IBinder clientToken, IBinder hostInputToken, int flags, int privateFlags, int type,
-            int inputFeatures, IBinder windowToken, IBinder inputTransferToken,
-            String inputHandleName, InputChannel outInputChannel) {
-        if (hostInputToken == null && !mCanAddInternalSystemWindow) {
+            IBinder clientToken, @Nullable InputTransferToken hostInputTransferToken, int flags,
+            int privateFlags, int type, int inputFeatures, IBinder windowToken,
+            InputTransferToken inputTransferToken, String inputHandleName,
+            InputChannel outInputChannel) {
+        if (hostInputTransferToken == null && !mCanAddInternalSystemWindow) {
             // Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to
             // embedded windows without providing a host window input token
             throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission");
@@ -926,7 +928,7 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             mService.grantInputChannel(this, mUid, mPid, displayId, surface, clientToken,
-                    hostInputToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0,
+                    hostInputTransferToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0,
                     type, inputFeatures, windowToken, inputTransferToken, inputHandleName,
                     outInputChannel);
         } finally {
@@ -947,7 +949,7 @@
     }
 
     @Override
-    public void grantEmbeddedWindowFocus(IWindow callingWindow, IBinder targetInputToken,
+    public void grantEmbeddedWindowFocus(IWindow callingWindow, InputTransferToken targetInputToken,
                                          boolean grantFocus) {
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -985,7 +987,7 @@
 
     @Override
     public boolean transferHostTouchGestureToEmbedded(IWindow hostWindow,
-            IBinder inputTransferToken) {
+            InputTransferToken inputTransferToken) {
         if (hostWindow == null) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5b51776..2bee095 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4386,10 +4386,12 @@
 
     void setHasBeenVisible(boolean hasBeenVisible) {
         mHasBeenVisible = hasBeenVisible;
-        if (!hasBeenVisible || mDeferTaskAppear) {
+        if (!hasBeenVisible) {
             return;
         }
-        sendTaskAppeared();
+        if (!mDeferTaskAppear) {
+            sendTaskAppeared();
+        }
         for (WindowContainer<?> parent = getParent(); parent != null; parent = parent.getParent()) {
             final Task parentTask = parent.asTask();
             if (parentTask == null) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2accf9a..5d9c42d 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -44,6 +44,7 @@
 import static android.view.WindowManager.transitTypeToString;
 import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
 import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
+import static android.window.TransitionInfo.FLAG_CONFIG_AT_END;
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -65,6 +66,7 @@
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -307,6 +309,12 @@
      */
     int mAnimationTrack = 0;
 
+    /**
+     * List of activities whose configurations are sent to the client at the end of the transition
+     * instead of immediately when the configuration changes.
+     */
+    ArrayList<ActivityRecord> mConfigAtEndActivities = null;
+
     Transition(@TransitionType int type, @TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
         mType = type;
@@ -484,6 +492,22 @@
         return mTargetDisplays.contains(dc);
     }
 
+    void setConfigAtEnd(@NonNull WindowContainer<?> wc) {
+        wc.forAllActivities(ar -> {
+            if (!ar.isVisible() || !ar.isVisibleRequested()) return;
+            if (mConfigAtEndActivities == null) {
+                mConfigAtEndActivities = new ArrayList<>();
+            }
+            if (mConfigAtEndActivities.contains(ar)) {
+                return;
+            }
+            mConfigAtEndActivities.add(ar);
+            ar.pauseConfigurationDispatch();
+        });
+        snapshotStartState(wc);
+        mChanges.get(wc).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
+    }
+
     /** Set a transition to be a seamless-rotation. */
     void setSeamlessRotation(@NonNull WindowContainer wc) {
         final ChangeInfo info = mChanges.get(wc);
@@ -644,20 +668,8 @@
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
                 mSyncId, wc);
-        // "snapshot" all parents (as potential promotion targets). Do this before checking
-        // if this is already a participant in case it has since been re-parented.
-        for (WindowContainer<?> curr = getAnimatableParent(wc);
-                curr != null && !mChanges.containsKey(curr);
-                curr = getAnimatableParent(curr)) {
-            final ChangeInfo info = new ChangeInfo(curr);
-            updateTransientFlags(info);
-            mChanges.put(curr, info);
-            if (isReadyGroup(curr)) {
-                mReadyTrackerOld.addGroup(curr);
-                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
-                                + " Transition %d with root=%s", mSyncId, curr);
-            }
-        }
+        // Snapshot before checking if this is a participant in case it has been re-parented.
+        snapshotStartState(getAnimatableParent(wc));
         if (mParticipants.contains(wc)) return;
         // Transient-hide may be hidden later, so no need to request redraw.
         if (!isInTransientHide(wc)) {
@@ -688,6 +700,22 @@
         }
     }
 
+    /** "snapshot" `wc` and all its parents (as potential promotion targets). */
+    private void snapshotStartState(@NonNull WindowContainer<?> wc) {
+        for (WindowContainer<?> curr = wc;
+                curr != null && !mChanges.containsKey(curr);
+                curr = getAnimatableParent(curr)) {
+            final ChangeInfo info = new ChangeInfo(curr);
+            updateTransientFlags(info);
+            mChanges.put(curr, info);
+            if (isReadyGroup(curr)) {
+                mReadyTrackerOld.addGroup(curr);
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
+                        + " Transition %d with root=%s", mSyncId, curr);
+            }
+        }
+    }
+
     private void updateTransientFlags(@NonNull ChangeInfo info) {
         final WindowContainer<?> wc = info.mContainer;
         // Only look at tasks, taskfragments, or activities
@@ -934,47 +962,60 @@
     }
 
     /**
+     * Populates `t` with instructions to reset surface transform of `change` so it matches
+     * the WM hierarchy. This "undoes" lingering state left by the animation.
+     */
+    private void resetSurfaceTransform(SurfaceControl.Transaction t, WindowContainer target,
+            SurfaceControl targetLeash) {
+        final Point tmpPos = new Point();
+        target.getRelativePosition(tmpPos);
+        t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
+        // No need to clip the display in case seeing the clipped content when during the
+        // display rotation. No need to clip activities because they rely on clipping on
+        // task layers.
+        if (target.asTaskFragment() == null) {
+            t.setCrop(targetLeash, null /* crop */);
+        } else {
+            // Crop to the resolved override bounds.
+            final Rect clipRect = target.getResolvedOverrideBounds();
+            t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
+        }
+        t.setMatrix(targetLeash, 1, 0, 0, 1);
+        // The bounds sent to the transition is always a real bounds. This means we lose
+        // information about "null" bounds (inheriting from parent). Core will fix-up
+        // non-organized window surface bounds; however, since Core can't touch organized
+        // surfaces, add the "inherit from parent" restoration here.
+        if (target.isOrganized() && target.matchParentBounds()) {
+            t.setWindowCrop(targetLeash, -1, -1);
+        }
+    }
+
+    /**
      * Build a transaction that "resets" all the re-parenting and layer changes. This is
      * intended to be applied at the end of the transition but before the finish callback. This
      * needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
      * Additionally, this gives shell the ability to better deal with merged transitions.
      */
     private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
-        final Point tmpPos = new Point();
         // usually only size 1
         final ArraySet<DisplayContent> displays = new ArraySet<>();
         for (int i = mTargets.size() - 1; i >= 0; --i) {
-            final WindowContainer target = mTargets.get(i).mContainer;
-            if (target.getParent() != null) {
-                final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
-                final SurfaceControl origParent = getOrigParentSurface(target);
-                // Ensure surfaceControls are re-parented back into the hierarchy.
-                t.reparent(targetLeash, origParent);
-                t.setLayer(targetLeash, target.getLastLayer());
-                target.getRelativePosition(tmpPos);
-                t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
-                // No need to clip the display in case seeing the clipped content when during the
-                // display rotation. No need to clip activities because they rely on clipping on
-                // task layers.
-                if (target.asTaskFragment() == null) {
-                    t.setCrop(targetLeash, null /* crop */);
-                } else {
-                    // Crop to the resolved override bounds.
-                    final Rect clipRect = target.getResolvedOverrideBounds();
-                    t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
-                }
-                t.setCornerRadius(targetLeash, 0);
-                t.setShadowRadius(targetLeash, 0);
-                t.setMatrix(targetLeash, 1, 0, 0, 1);
-                t.setAlpha(targetLeash, 1);
-                // The bounds sent to the transition is always a real bounds. This means we lose
-                // information about "null" bounds (inheriting from parent). Core will fix-up
-                // non-organized window surface bounds; however, since Core can't touch organized
-                // surfaces, add the "inherit from parent" restoration here.
-                if (target.isOrganized() && target.matchParentBounds()) {
-                    t.setWindowCrop(targetLeash, -1, -1);
-                }
-                displays.add(target.getDisplayContent());
+            final WindowContainer<?> target = mTargets.get(i).mContainer;
+            if (target.getParent() == null) continue;
+            final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
+            final SurfaceControl origParent = getOrigParentSurface(target);
+            // Ensure surfaceControls are re-parented back into the hierarchy.
+            t.reparent(targetLeash, origParent);
+            t.setLayer(targetLeash, target.getLastLayer());
+            t.setCornerRadius(targetLeash, 0);
+            t.setShadowRadius(targetLeash, 0);
+            t.setAlpha(targetLeash, 1);
+            displays.add(target.getDisplayContent());
+            // For config-at-end, the end-transform will be reset after the config is actually
+            // applied in the client (since the transform depends on config). The other properties
+            // remain here because shell might want to persistently override them.
+            if ((mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) {
+                resetSurfaceTransform(t, target, targetLeash);
             }
         }
         // Remove screenshot layers if necessary
@@ -1304,6 +1345,8 @@
             mController.mAtm.mRootWindowContainer.rankTaskLayers();
         }
 
+        commitConfigAtEndActivities();
+
         // dispatch legacy callback in a different loop. This is because multiple legacy handlers
         // (fixed-rotation/displaycontent) make global changes, so we want to ensure that we've
         // processed all the participants first (in particular, we want to trigger pip-enter first)
@@ -1421,6 +1464,52 @@
         mController.updateAnimatingState();
     }
 
+    private void commitConfigAtEndActivities() {
+        if (mConfigAtEndActivities == null || mConfigAtEndActivities.isEmpty()) {
+            return;
+        }
+        final SurfaceControl.Transaction t =
+                mController.mAtm.mWindowManager.mTransactionFactory.get();
+        for (int i = 0; i < mTargets.size(); ++i) {
+            final WindowContainer target = mTargets.get(i).mContainer;
+            if (target.getParent() == null || (mTargets.get(i).mFlags
+                    & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) {
+                continue;
+            }
+            final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
+            // Reset surface state here (since it was skipped in buildFinishTransaction). Since
+            // we are resuming config to the "current" state, we have to calculate the matching
+            // surface state now (rather than snapshotting it at animation start).
+            resetSurfaceTransform(t, target, targetLeash);
+        }
+
+        // Now we resume the configuration dispatch, wait until the now resumed configs have been
+        // drawn, and then apply everything together.
+        final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet(
+                new BLASTSyncEngine.TransactionReadyListener() {
+                    @Override
+                    public void onTransactionReady(int mSyncId,
+                            SurfaceControl.Transaction transaction) {
+                        t.merge(transaction);
+                        t.apply();
+                    }
+
+                    @Override
+                    public void onTransactionCommitTimeout() {
+                        t.apply();
+                    }
+                }, "ConfigAtTransitEnd");
+        final int syncId = sg.mSyncId;
+        mSyncEngine.startSyncSet(sg, BLAST_TIMEOUT_DURATION, true /* parallel */);
+        mSyncEngine.setSyncMethod(syncId, BLASTSyncEngine.METHOD_BLAST);
+        for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
+            final ActivityRecord ar = mConfigAtEndActivities.get(i);
+            mSyncEngine.addToSyncSet(syncId, ar);
+            ar.resumeConfigurationDispatch();
+        }
+        mSyncEngine.setReady(syncId);
+    }
+
     @Nullable
     private ActivityRecord getVisibleTransientLaunch(TaskDisplayArea taskDisplayArea) {
         if (mTransientLaunches == null) return null;
@@ -1546,6 +1635,12 @@
 
         if (mState == STATE_ABORT) {
             mController.onAbort(this);
+            if (mConfigAtEndActivities != null) {
+                for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
+                    mConfigAtEndActivities.get(i).resumeConfigurationDispatch();
+                }
+                mConfigAtEndActivities = null;
+            }
             primaryDisplay.getPendingTransaction().merge(transaction);
             mSyncId = -1;
             mOverrideOptions = null;
@@ -2291,6 +2386,11 @@
             } else {
                 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
             }
+            final ActivityRecord ar = targetChange.mContainer.asActivityRecord();
+            if ((ar != null && ar.isConfigurationDispatchPaused())
+                    || ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0)) {
+                parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
+            }
         }
     }
 
@@ -2940,6 +3040,9 @@
         /** Whether this change's container moved to the top. */
         private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20;
 
+        /** Whether this change contains config-at-end members. */
+        private static final int FLAG_CHANGE_CONFIG_AT_END = 0x40;
+
         @IntDef(prefix = { "FLAG_" }, value = {
                 FLAG_NONE,
                 FLAG_SEAMLESS_ROTATION,
@@ -2947,7 +3050,8 @@
                 FLAG_ABOVE_TRANSIENT_LAUNCH,
                 FLAG_CHANGE_NO_ANIMATION,
                 FLAG_CHANGE_YES_ANIMATION,
-                FLAG_CHANGE_MOVED_TO_TOP
+                FLAG_CHANGE_MOVED_TO_TOP,
+                FLAG_CHANGE_CONFIG_AT_END
         })
         @Retention(RetentionPolicy.SOURCE)
         @interface Flag {}
@@ -3095,6 +3199,9 @@
                     flags |= FLAG_IS_VOICE_INTERACTION;
                 }
                 flags |= record.mTransitionChangeFlags;
+                if (record.isConfigurationDispatchPaused()) {
+                    flags |= FLAG_CONFIG_AT_END;
+                }
             }
             final TaskFragment taskFragment = wc.asTaskFragment();
             if (taskFragment != null && task == null) {
@@ -3140,6 +3247,9 @@
             if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) {
                 flags |= FLAG_MOVED_TO_TOP;
             }
+            if ((mFlags & FLAG_CHANGE_CONFIG_AT_END) != 0) {
+                flags |= FLAG_CONFIG_AT_END;
+            }
             return flags;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 366e1bb..4ea76e1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -310,6 +310,7 @@
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
+import android.window.InputTransferToken;
 import android.window.ScreenCapture;
 import android.window.SystemPerformanceHinter;
 import android.window.TaskSnapshot;
@@ -9011,19 +9012,21 @@
      * views.
      */
     void grantInputChannel(Session session, int callingUid, int callingPid, int displayId,
-            SurfaceControl surface, IBinder clientToken, IBinder hostInputToken,
-            int flags, int privateFlags, int inputFeatures, int type, IBinder windowToken,
-            IBinder inputTransferToken, String inputHandleName, InputChannel outInputChannel) {
+            SurfaceControl surface, IBinder clientToken,
+            @Nullable InputTransferToken hostInputTransferToken, int flags, int privateFlags,
+            int inputFeatures, int type, IBinder windowToken, InputTransferToken inputTransferToken,
+            String inputHandleName, InputChannel outInputChannel) {
         final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type);
         final InputApplicationHandle applicationHandle;
         final String name;
         Objects.requireNonNull(outInputChannel);
         synchronized (mGlobalLock) {
+            WindowState hostWindowState = hostInputTransferToken != null
+                    ? mInputToWindowMap.get(hostInputTransferToken.mToken) : null;
             EmbeddedWindowController.EmbeddedWindow win =
                     new EmbeddedWindowController.EmbeddedWindow(session, this, clientToken,
-                            mInputToWindowMap.get(hostInputToken), callingUid, callingPid,
-                            sanitizedType, displayId, inputTransferToken, inputHandleName,
-                            (flags & FLAG_NOT_FOCUSABLE) == 0);
+                            hostWindowState, callingUid, callingPid, sanitizedType, displayId,
+                            inputTransferToken, inputHandleName, (flags & FLAG_NOT_FOCUSABLE) == 0);
             win.openInputChannel(outInputChannel);
             mEmbeddedWindowController.add(outInputChannel.getToken(), win);
             applicationHandle = win.getApplicationHandle();
@@ -9068,7 +9071,7 @@
     }
 
     boolean transferHostTouchGestureToEmbedded(Session session, IWindow hostWindow,
-            IBinder inputTransferToken) {
+            InputTransferToken inputTransferToken) {
         final IBinder hostInputChannel, embeddedInputChannel;
         synchronized (mGlobalLock) {
             final WindowState hostWindowState = windowForClientLocked(session, hostWindow, false);
@@ -9484,7 +9487,8 @@
         return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId);
     }
 
-    void grantEmbeddedWindowFocus(Session session, IBinder inputTransferToken, boolean grantFocus) {
+    void grantEmbeddedWindowFocus(Session session, InputTransferToken inputTransferToken,
+            boolean grantFocus) {
         synchronized (mGlobalLock) {
             final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
                     mEmbeddedWindowController.getByInputTransferToken(inputTransferToken);
@@ -9533,7 +9537,7 @@
     }
 
     void grantEmbeddedWindowFocus(Session session, IWindow callingWindow,
-            IBinder inputTransferToken, boolean grantFocus) {
+            InputTransferToken inputTransferToken, boolean grantFocus) {
         synchronized (mGlobalLock) {
             final WindowState hostWindow =
                     windowForClientLocked(session, callingWindow, false /* throwOnError*/);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 8cd399f..a8de919 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -583,8 +583,21 @@
             }
             final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
             final int hopSize = hops.size();
-            Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
-                    t.getChanges().entrySet().iterator();
+            Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries;
+            if (transition != null) {
+                // Mark any config-at-end containers before applying config changes so that
+                // the config changes don't dispatch to client.
+                entries = t.getChanges().entrySet().iterator();
+                while (entries.hasNext()) {
+                    final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
+                            entries.next();
+                    if (!entry.getValue().getConfigAtTransitionEnd()) continue;
+                    final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
+                    if (wc == null || !wc.isAttached()) continue;
+                    transition.setConfigAtEnd(wc);
+                }
+            }
+            entries = t.getChanges().entrySet().iterator();
             while (entries.hasNext()) {
                 final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();
                 final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 4c74878..5588276 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -1662,6 +1662,9 @@
     ): Int =
         state.userStates[userId]?.appIdPermissionFlags?.get(appId).getWithDefault(permissionName, 0)
 
+    fun GetStateScope.getAllPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
+        state.userStates[userId]?.appIdPermissionFlags?.get(appId)
+
     fun MutateStateScope.setPermissionFlags(
         appId: Int,
         userId: Int,
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index 3284cf1..3e3047c 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -95,8 +95,8 @@
     ) {
         packageNames.forEachIndexed { _, packageName ->
             // The package may still be removed even if it was once notified as installed.
-            val packageState = newState.externalState.packageStates[packageName]
-                ?: return@forEachIndexed
+            val packageState =
+                newState.externalState.packageStates[packageName] ?: return@forEachIndexed
             trimPermissionStates(packageState.appId)
         }
     }
@@ -226,6 +226,16 @@
         return flags
     }
 
+    fun GetStateScope.getAllPermissionFlags(
+        appId: Int,
+        persistentDeviceId: String,
+        userId: Int
+    ): IndexedMap<String, Int>? =
+        state.userStates[userId]
+            ?.appIdDevicePermissionFlags
+            ?.get(appId)
+            ?.get(persistentDeviceId)
+
     fun MutateStateScope.setPermissionFlags(
         appId: Int,
         deviceId: String,
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 1241ce6..2f5c109 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -45,6 +45,7 @@
 import android.permission.IOnPermissionsChangeListener
 import android.permission.PermissionControllerManager
 import android.permission.PermissionManager
+import android.permission.PermissionManager.PermissionState
 import android.permission.flags.Flags
 import android.provider.Settings
 import android.util.ArrayMap
@@ -1135,6 +1136,53 @@
         }
     }
 
+    override fun getAllPermissionStates(
+        packageName: String,
+        persistentDeviceId: String,
+        userId: Int
+    ): Map<String, PermissionState> {
+        if (!userManagerInternal.exists(userId)) {
+            Slog.w(LOG_TAG, "getAllPermissionStates: Unknown user $userId")
+            return emptyMap()
+        }
+        enforceCallingOrSelfCrossUserPermission(
+            userId,
+            enforceFullPermission = true,
+            enforceShellRestriction = false,
+            "getAllPermissionStates"
+        )
+        enforceCallingOrSelfAnyPermission(
+            "getAllPermissionStates",
+            Manifest.permission.GET_RUNTIME_PERMISSIONS
+        )
+
+        val packageState =
+            packageManagerLocal.withFilteredSnapshot().use { it.getPackageState(packageName) }
+        if (packageState == null) {
+            Slog.w(LOG_TAG, "getAllPermissionStates: Unknown package $packageName")
+            return emptyMap()
+        }
+
+        val permissionFlagsMap =
+            service.getState {
+                if (persistentDeviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) {
+                    with(policy) { getAllPermissionFlags(packageState.appId, userId) }
+                } else {
+                    with(devicePolicy) {
+                        getAllPermissionFlags(packageState.appId, persistentDeviceId, userId)
+                    }
+                }
+            } ?: return emptyMap()
+
+        val permissionStates = ArrayMap<String, PermissionState>()
+        permissionFlagsMap.forEachIndexed { _, permissionName, flags ->
+            val granted = PermissionFlags.isPermissionGranted(flags)
+            val apiFlags = PermissionFlags.toApiFlags(flags)
+            permissionStates[permissionName] = PermissionState(granted, apiFlags)
+        }
+        return permissionStates
+    }
+
     override fun isPermissionRevokedByPolicy(
         packageName: String,
         permissionName: String,
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeMapTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeMapTest.java
new file mode 100644
index 0000000..3bb6712
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeMapTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+import java.util.List;
+public final class AdditionalSubtypeMapTest {
+
+    private static final String TEST_IME1_ID = "com.android.test.inputmethod/.TestIme1";
+    private static final String TEST_IME2_ID = "com.android.test.inputmethod/.TestIme2";
+
+    private static InputMethodSubtype createTestSubtype(String locale) {
+        return new InputMethodSubtype
+                .InputMethodSubtypeBuilder()
+                .setSubtypeNameResId(0)
+                .setSubtypeIconResId(0)
+                .setSubtypeLocale(locale)
+                .setIsAsciiCapable(true)
+                .build();
+    }
+
+    private static final InputMethodSubtype TEST_SUBTYPE_EN_US = createTestSubtype("en_US");
+    private static final InputMethodSubtype TEST_SUBTYPE_JA_JP = createTestSubtype("ja_JP");
+
+    private static final List<InputMethodSubtype> TEST_SUBTYPE_LIST1 = List.of(TEST_SUBTYPE_EN_US);
+    private static final List<InputMethodSubtype> TEST_SUBTYPE_LIST2 = List.of(TEST_SUBTYPE_JA_JP);
+
+    private static ArrayMap<String, List<InputMethodSubtype>> mapOf(
+            @NonNull String key1, @NonNull List<InputMethodSubtype> value1) {
+        final ArrayMap<String, List<InputMethodSubtype>> map = new ArrayMap<>();
+        map.put(key1, value1);
+        return map;
+    }
+
+    private static ArrayMap<String, List<InputMethodSubtype>> mapOf(
+            @NonNull String key1, @NonNull List<InputMethodSubtype> value1,
+            @NonNull String key2, @NonNull List<InputMethodSubtype> value2) {
+        final ArrayMap<String, List<InputMethodSubtype>> map = new ArrayMap<>();
+        map.put(key1, value1);
+        map.put(key2, value2);
+        return map;
+    }
+
+    @Test
+    public void testOfReturnsEmptyInstance() {
+        assertThat(AdditionalSubtypeMap.of(new ArrayMap<>()))
+                .isSameInstanceAs(AdditionalSubtypeMap.EMPTY_MAP);
+    }
+
+    @Test
+    public void testOfReturnsNewInstance() {
+        final AdditionalSubtypeMap instance = AdditionalSubtypeMap.of(
+                mapOf(TEST_IME1_ID, TEST_SUBTYPE_LIST1));
+        assertThat(instance.keySet()).containsExactly(TEST_IME1_ID);
+        assertThat(instance.get(TEST_IME1_ID)).containsExactlyElementsIn(TEST_SUBTYPE_LIST1);
+    }
+
+    @Test
+    public void testCloneWithRemoveOrSelfReturnsEmptyInstance() {
+        final AdditionalSubtypeMap original = AdditionalSubtypeMap.of(
+                mapOf(TEST_IME1_ID, TEST_SUBTYPE_LIST1));
+        final AdditionalSubtypeMap result = original.cloneWithRemoveOrSelf(TEST_IME1_ID);
+        assertThat(result).isSameInstanceAs(AdditionalSubtypeMap.EMPTY_MAP);
+    }
+
+    @Test
+    public void testCloneWithRemoveOrSelfWithMultipleKeysReturnsEmptyInstance() {
+        final AdditionalSubtypeMap original = AdditionalSubtypeMap.of(
+                mapOf(TEST_IME1_ID, TEST_SUBTYPE_LIST1, TEST_IME2_ID, TEST_SUBTYPE_LIST2));
+        final AdditionalSubtypeMap result = original.cloneWithRemoveOrSelf(
+                List.of(TEST_IME1_ID, TEST_IME2_ID));
+        assertThat(result).isSameInstanceAs(AdditionalSubtypeMap.EMPTY_MAP);
+    }
+
+    @Test
+    public void testCloneWithRemoveOrSelfReturnsNewInstance() {
+        final AdditionalSubtypeMap original = AdditionalSubtypeMap.of(
+                mapOf(TEST_IME1_ID, TEST_SUBTYPE_LIST1, TEST_IME2_ID, TEST_SUBTYPE_LIST2));
+        final AdditionalSubtypeMap result = original.cloneWithRemoveOrSelf(TEST_IME1_ID);
+        assertThat(result.keySet()).containsExactly(TEST_IME2_ID);
+        assertThat(result.get(TEST_IME2_ID)).containsExactlyElementsIn(TEST_SUBTYPE_LIST2);
+    }
+
+    @Test
+    public void testCloneWithPutWithNewKey() {
+        final AdditionalSubtypeMap original = AdditionalSubtypeMap.of(
+                mapOf(TEST_IME1_ID, TEST_SUBTYPE_LIST1));
+        final AdditionalSubtypeMap result = original.cloneWithPut(TEST_IME2_ID, TEST_SUBTYPE_LIST2);
+        assertThat(result.keySet()).containsExactly(TEST_IME1_ID, TEST_IME2_ID);
+        assertThat(result.get(TEST_IME1_ID)).containsExactlyElementsIn(TEST_SUBTYPE_LIST1);
+        assertThat(result.get(TEST_IME2_ID)).containsExactlyElementsIn(TEST_SUBTYPE_LIST2);
+    }
+
+    @Test
+    public void testCloneWithPutWithExistingKey() {
+        final AdditionalSubtypeMap original = AdditionalSubtypeMap.of(
+                mapOf(TEST_IME1_ID, TEST_SUBTYPE_LIST1, TEST_IME2_ID, TEST_SUBTYPE_LIST2));
+        final AdditionalSubtypeMap result = original.cloneWithPut(TEST_IME2_ID, TEST_SUBTYPE_LIST1);
+        assertThat(result.keySet()).containsExactly(TEST_IME1_ID, TEST_IME2_ID);
+        assertThat(result.get(TEST_IME1_ID)).containsExactlyElementsIn(TEST_SUBTYPE_LIST1);
+        assertThat(result.get(TEST_IME2_ID)).containsExactlyElementsIn(TEST_SUBTYPE_LIST1);
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
index 0edb3df..63224bb 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
@@ -55,9 +55,9 @@
         // Save & load.
         AtomicFile atomicFile = new AtomicFile(
                 new File(InstrumentationRegistry.getContext().getCacheDir(), "subtypes.xml"));
-        AdditionalSubtypeUtils.saveToFile(allSubtypes, InputMethodMap.of(methodMap), atomicFile);
-        ArrayMap<String, List<InputMethodSubtype>> loadedSubtypes = new ArrayMap<>();
-        AdditionalSubtypeUtils.loadFromFile(loadedSubtypes, atomicFile);
+        AdditionalSubtypeUtils.saveToFile(AdditionalSubtypeMap.of(allSubtypes),
+                InputMethodMap.of(methodMap), atomicFile);
+        AdditionalSubtypeMap loadedSubtypes = AdditionalSubtypeUtils.loadFromFile(atomicFile);
 
         // Verifies the loaded data.
         assertEquals(1, loadedSubtypes.size());
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
index 71752ba..2ea2e22 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
@@ -25,10 +25,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.util.ArrayMap;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSubtype;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -124,10 +122,8 @@
 
     private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList,
             List<String> enabledComponents) {
-        final ArrayMap<String, List<InputMethodSubtype>> emptyAdditionalSubtypeMap =
-                new ArrayMap<>();
         final InputMethodMap methodMap = InputMethodManagerService.filterInputMethodServices(
-                emptyAdditionalSubtypeMap, enabledComponents, mContext, resolveInfoList);
+                AdditionalSubtypeMap.EMPTY_MAP, enabledComponents, mContext, resolveInfoList);
         return methodMap.values();
     }
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index a8100af..66e0717 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -18,6 +18,10 @@
 
 import android.content.Context
 import android.content.Intent
+import android.content.UriRelativeFilter
+import android.content.UriRelativeFilterGroup
+import android.content.UriRelativeFilterGroupParcel
+import android.content.pm.Flags
 import android.content.pm.PackageManager
 import android.content.pm.verify.domain.DomainOwner
 import android.content.pm.verify.domain.DomainVerificationInfo
@@ -25,8 +29,10 @@
 import android.content.pm.verify.domain.DomainVerificationUserState
 import android.content.pm.verify.domain.IDomainVerificationManager
 import android.os.Build
-import android.os.PatternMatcher
+import android.os.Bundle
+import android.os.PatternMatcher.PATTERN_LITERAL
 import android.os.Process
+import android.platform.test.annotations.RequiresFlagsEnabled
 import android.util.ArraySet
 import android.util.SparseArray
 import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
@@ -68,6 +74,63 @@
         private val DOMAIN_4 = "four.$DOMAIN_BASE"
     }
 
+    @RequiresFlagsEnabled(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+    @Test
+    fun updateUriRelativeFilterGroups() {
+        val pkgWithDomains = mockPkgState(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
+        val service = makeService(pkgWithDomains).apply {
+            addPackages(pkgWithDomains)
+        }
+
+        val bundle = service.getUriRelativeFilterGroups(PKG_ONE, listOf(DOMAIN_1, DOMAIN_2))
+        assertThat(bundle.keySet()).containsExactlyElementsIn(listOf(DOMAIN_1, DOMAIN_2))
+        assertThat(bundle.getParcelableArrayList(DOMAIN_1, UriRelativeFilterGroup::class.java))
+            .isEmpty()
+        assertThat(bundle.getParcelableArrayList(DOMAIN_2, UriRelativeFilterGroup::class.java))
+            .isEmpty()
+
+        val pathGroup = UriRelativeFilterGroup(UriRelativeFilterGroup.ACTION_ALLOW)
+        pathGroup.addUriRelativeFilter(
+            UriRelativeFilter(UriRelativeFilter.PATH, PATTERN_LITERAL, "path")
+        )
+        val queryGroup = UriRelativeFilterGroup(UriRelativeFilterGroup.ACTION_BLOCK)
+        queryGroup.addUriRelativeFilter(
+            UriRelativeFilter(UriRelativeFilter.QUERY, PATTERN_LITERAL, "query")
+        )
+        val fragmentGroup = UriRelativeFilterGroup(UriRelativeFilterGroup.ACTION_ALLOW)
+        fragmentGroup.addUriRelativeFilter(
+            UriRelativeFilter(UriRelativeFilter.FRAGMENT, PATTERN_LITERAL, "fragment")
+        )
+
+        assertGroups(service, arrayListOf(pathGroup))
+        assertGroups(service, arrayListOf(queryGroup, pathGroup))
+        assertGroups(service, arrayListOf(queryGroup, fragmentGroup, pathGroup))
+    }
+
+    private fun assertGroups(
+        service: DomainVerificationService,
+        groups: List<UriRelativeFilterGroup>
+    ) {
+        val bundle = Bundle()
+        bundle.putParcelableList(DOMAIN_1, UriRelativeFilterGroup.groupsToParcels(groups))
+        service.setUriRelativeFilterGroups(PKG_ONE, bundle)
+        val fetchedBundle = service.getUriRelativeFilterGroups(PKG_ONE, listOf(DOMAIN_1))
+        assertThat(fetchedBundle.keySet()).containsExactlyElementsIn(bundle.keySet())
+        assertThat(
+            UriRelativeFilterGroup.parcelsToGroups(
+                fetchedBundle.getParcelableArrayList(
+                    DOMAIN_1,
+                    UriRelativeFilterGroupParcel::class.java)
+            )
+        ).containsExactlyElementsIn(
+            UriRelativeFilterGroup.parcelsToGroups(
+                bundle.getParcelableArrayList(
+                    DOMAIN_1,
+                    UriRelativeFilterGroupParcel::class.java)
+            )
+        ).inOrder()
+    }
+
     @Test
     fun queryValidVerificationPackageNames() {
         val pkgWithDomains = mockPkgState(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
@@ -484,6 +547,7 @@
         DomainVerificationService(mockThrowOnUnmocked {
             // Assume the test has every permission necessary
             whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
+            whenever(enforceCallingOrSelfPermission(anyString(), anyString()))
             whenever(checkPermission(anyString(), anyInt(), anyInt())) {
                 PackageManager.PERMISSION_GRANTED
             }
@@ -539,7 +603,7 @@
                                     addCategory(Intent.CATEGORY_DEFAULT)
                                     addDataScheme("http")
                                     addDataScheme("https")
-                                    addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+                                    addDataPath("/sub", PATTERN_LITERAL)
                                     addDataAuthority(it, null)
                                 }
                             }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
index 65b99c5..4fa4190 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -16,7 +16,12 @@
 
 package com.android.server.pm.test.verify.domain
 
+import android.content.UriRelativeFilter
+import android.content.UriRelativeFilter.PATH
+import android.content.UriRelativeFilterGroup
+import android.content.UriRelativeFilterGroup.ACTION_ALLOW
 import android.content.pm.verify.domain.DomainVerificationState
+import android.os.PatternMatcher.PATTERN_LITERAL
 import android.os.UserHandle
 import android.util.ArrayMap
 import android.util.SparseArray
@@ -157,7 +162,7 @@
 
     @Test
     fun writeStateSignatureIfFunctionReturnsNull() {
-        val (attached, pending, restored) = mockWriteValues  { "SIGNATURE_$it" }
+        val (attached, pending, restored) = mockWriteValues { "SIGNATURE_$it" }
         val file = tempFolder.newFile().writeXml {
             DomainVerificationPersistence.writeToXml(it, attached, pending, restored,
                     UserHandle.USER_ALL) { null }
@@ -313,6 +318,9 @@
                     addHosts(setOf("$packageName-user.com"))
                     isLinkHandlingAllowed = true
                 }
+                val group = UriRelativeFilterGroup(ACTION_ALLOW)
+                group.addUriRelativeFilter(UriRelativeFilter(PATH, PATTERN_LITERAL, "test"))
+                uriRelativeFilterGroupMap.put("example.com", listOf(group))
             }
 
     private fun pkgName(id: Int) = "$PKG_PREFIX.pkg$id"
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
index d781433..9e98105 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
@@ -31,6 +31,7 @@
 
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
+import android.provider.Settings;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
@@ -391,6 +392,16 @@
     }
 
     @Test
+    public void mediaProjectionOnStart_disabledViaDevOption_noBlockedPackages() {
+        mockDisabledViaDevelopOption();
+        setupSensitiveNotification();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
     public void nlsOnListenerConnected_projectionNotStarted_noop() {
         // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
         // as non-sensitive
@@ -484,6 +495,18 @@
     }
 
     @Test
+    public void nlsOnListenerConnected_disabledViaDevOption_noBlockedPackages() {
+        mockDisabledViaDevelopOption();
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
     public void nlsOnNotificationRankingUpdate_projectionNotStarted_noop() {
         // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
         // as non-sensitive
@@ -599,6 +622,19 @@
     }
 
     @Test
+    public void nlsOnNotificationRankingUpdate_disabledViaDevOption_noBlockedPackages() {
+        mockDisabledViaDevelopOption();
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
     public void nlsOnNotificationPosted_projectionNotStarted_noop() {
         // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
         // as non-sensitive
@@ -697,4 +733,26 @@
 
         verifyZeroInteractions(mWindowManager);
     }
+
+    @Test
+    public void nlsOnNotificationPosted_disabledViaDevOption_noBlockedPackages() {
+        mockDisabledViaDevelopOption();
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    private void mockDisabledViaDevelopOption() {
+        // mContext (TestableContext) uses [TestableSettingsProvider] and it will be cleared after
+        // the test
+        Settings.Global.putInt(
+                mContext.getContentResolver(),
+                Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
+                1);
+    }
 }
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 64fef68..1de049e 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -8,6 +8,37 @@
     srcs: [
         "src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java",
         "src/com/android/server/power/stats/AggregatedPowerStatsTest.java",
+        "src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/AudioPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/BatteryChargeCalculatorTest.java",
+        "src/com/android/server/power/stats/BatteryStatsCounterTest.java",
+        "src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java",
+        "src/com/android/server/power/stats/BatteryStatsDualTimerTest.java",
+        "src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java",
+        "src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java",
+        "src/com/android/server/power/stats/BatteryStatsHistoryTest.java",
+        "src/com/android/server/power/stats/BatteryStatsImplTest.java",
+        "src/com/android/server/power/stats/BatteryStatsNoteTest.java",
+        "src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java",
+        "src/com/android/server/power/stats/BatteryStatsSensorTest.java",
+        "src/com/android/server/power/stats/BatteryStatsServTest.java",
+        "src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java",
+        "src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java",
+        "src/com/android/server/power/stats/BatteryStatsTimerTest.java",
+        "src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java",
+        "src/com/android/server/power/stats/BatteryUsageStatsTest.java",
+        "src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/CameraPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java",
+        "src/com/android/server/power/stats/CpuPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java",
+        "src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/GnssPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/IdlePowerCalculatorTest.java",
+        "src/com/android/server/power/stats/LongSamplingCounterArrayTest.java",
+        "src/com/android/server/power/stats/LongSamplingCounterTest.java",
+        "src/com/android/server/power/stats/MemoryPowerCalculatorTest.java",
         "src/com/android/server/power/stats/MultiStateStatsTest.java",
         "src/com/android/server/power/stats/PowerStatsAggregatorTest.java",
         "src/com/android/server/power/stats/PowerStatsCollectorTest.java",
@@ -15,6 +46,12 @@
         "src/com/android/server/power/stats/PowerStatsSchedulerTest.java",
         "src/com/android/server/power/stats/PowerStatsStoreTest.java",
         "src/com/android/server/power/stats/PowerStatsUidResolverTest.java",
+        "src/com/android/server/power/stats/ScreenPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/SensorPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/UserPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/VideoPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/WakelockPowerCalculatorTest.java",
+        "src/com/android/server/power/stats/WifiPowerCalculatorTest.java",
     ],
 }
 
@@ -26,10 +63,6 @@
         "src/**/*.java",
     ],
 
-    exclude_srcs: [
-        ":power_stats_ravenwood_tests",
-    ],
-
     static_libs: [
         "services.core",
         "coretests-aidl",
@@ -41,6 +74,7 @@
         "androidx.test.ext.truth",
         "androidx.test.uiautomator_uiautomator",
         "mockito-target-minus-junit4",
+        "ravenwood-junit",
         "servicestests-utils",
         "platform-test-annotations",
         "flag-junit",
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
index 319a280..f74cfae 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
@@ -21,6 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.BatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
@@ -34,10 +35,15 @@
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class AmbientDisplayPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
     private static final long MINUTE_IN_MS = 60 * 1000;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0, 10.0)
             .setNumDisplays(1);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java
index fb367b2..ce451c2 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java
@@ -21,6 +21,7 @@
 import android.os.BatteryConsumer;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -34,11 +35,16 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class AudioPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_AUDIO, 360.0);
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
index 3f058a2..3ab1c2e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
@@ -21,6 +21,7 @@
 
 import android.os.BatteryManager;
 import android.os.BatteryUsageStats;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -34,9 +35,14 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class BatteryChargeCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
                     .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
 
@@ -46,15 +52,17 @@
 
         final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
-        batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
-                /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0,
-                1_000_000, 1_000_000, 1_000_000);
-        batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
-                /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0,
-                1_500_000, 1_500_000, 1_500_000);
-        batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
-                /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0,
-                2_000_000, 2_000_000, 2_000_000);
+        synchronized (batteryStats) {
+            batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
+                    /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0,
+                    1_000_000, 1_000_000, 1_000_000);
+            batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
+                    /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0,
+                    1_500_000, 1_500_000, 1_500_000);
+            batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
+                    /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0,
+                    2_000_000, 2_000_000, 2_000_000);
+        }
 
         mStatsRule.setTime(5_000_000, 5_000_000);
         BatteryChargeCalculator calculator = new BatteryChargeCalculator();
@@ -73,10 +81,11 @@
         assertThat(batteryUsageStats.getChargeTimeRemainingMs()).isEqualTo(-1);
 
         // Plug in
-        batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_CHARGING, 100,
-                BatteryManager.BATTERY_PLUGGED_USB, 80, 72, 3700, 2_400_000, 4_000_000, 100,
-                4_000_000, 4_000_000, 4_000_000);
-
+        synchronized (batteryStats) {
+            batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_CHARGING, 100,
+                    BatteryManager.BATTERY_PLUGGED_USB, 80, 72, 3700, 2_400_000, 4_000_000, 100,
+                    4_000_000, 4_000_000, 4_000_000);
+        }
         batteryUsageStats = mStatsRule.apply(calculator);
 
         assertThat(batteryUsageStats.getChargeTimeRemainingMs()).isEqualTo(100_000);
@@ -86,15 +95,17 @@
     public void testDischargeTotals_chargeUahUnavailable() {
         final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
-        batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
-                /* plugType */ 0, 90, 72, 3700, 0, 0, 0,
-                1_000_000, 1_000_000, 1_000_000);
-        batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
-                /* plugType */ 0, 85, 72, 3700, 0, 0, 0,
-                1_500_000, 1_500_000, 1_500_000);
-        batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
-                /* plugType */ 0, 80, 72, 3700, 0, 0, 0,
-                2_000_000, 2_000_000, 2_000_000);
+        synchronized (batteryStats) {
+            batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
+                    /* plugType */ 0, 90, 72, 3700, 0, 0, 0,
+                    1_000_000, 1_000_000, 1_000_000);
+            batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
+                    /* plugType */ 0, 85, 72, 3700, 0, 0, 0,
+                    1_500_000, 1_500_000, 1_500_000);
+            batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
+                    /* plugType */ 0, 80, 72, 3700, 0, 0, 0,
+                    2_000_000, 2_000_000, 2_000_000);
+        }
 
         BatteryChargeCalculator calculator = new BatteryChargeCalculator();
         BatteryUsageStats batteryUsageStats = mStatsRule.apply(calculator);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 9c2834d3..997b771 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -215,7 +215,7 @@
 
     public class TestBatteryStatsImpl extends BatteryStatsImpl {
         public TestBatteryStatsImpl(Context context) {
-            super(Clock.SYSTEM_CLOCK, null, null, null);
+            super(Clock.SYSTEM_CLOCK, null, null, null, null, null, null);
             mPowerProfile = new PowerProfile(context, true /* forTest */);
 
             SparseArray<int[]> cpusByPolicy = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
index f9f32b2..6e62147 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
@@ -40,6 +40,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 import android.util.SparseLongArray;
 import android.view.Display;
@@ -56,6 +57,7 @@
 import com.android.internal.util.ArrayUtils;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -84,7 +86,13 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@SuppressWarnings("SynchronizeOnNonFinalField")
 public class BatteryStatsCpuTimesTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     @Mock
     KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader;
     @Mock
@@ -128,7 +136,9 @@
         initKernelCpuSpeedReaders(numClusters);
 
         // RUN
-        mBatteryStatsImpl.updateCpuTimeLocked(false, false, null);
+        synchronized (mBatteryStatsImpl) {
+            mBatteryStatsImpl.updateCpuTimeLocked(false, false, null);
+        }
 
         // VERIFY
         verify(mCpuUidUserSysTimeReader).readDelta(anyBoolean(), isNull());
@@ -147,7 +157,9 @@
         mBatteryStatsImpl.setOnBatteryInternal(true);
 
         // RUN
-        mBatteryStatsImpl.updateCpuTimeLocked(true, false, null);
+        synchronized (mBatteryStatsImpl) {
+            mBatteryStatsImpl.updateCpuTimeLocked(true, false, null);
+        }
 
         // VERIFY
         verify(mUserInfoProvider).refreshUserIds();
@@ -239,7 +251,7 @@
         mBatteryStatsImpl.updateClusterSpeedTimes(updatedUids, true, null);
 
         // VERIFY
-        int totalClustersTimeMs = 0;
+        long totalClustersTimeMs = 0;
         for (int i = 0; i < clusterSpeedTimesMs.length; ++i) {
             for (int j = 0; j < clusterSpeedTimesMs[i].length; ++j) {
                 totalClustersTimeMs += clusterSpeedTimesMs[i][j];
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index bf5bf36..395b3aa 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -21,6 +21,7 @@
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -29,6 +30,7 @@
 import com.android.internal.os.MonotonicClock;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -42,6 +44,11 @@
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class BatteryStatsHistoryIteratorTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
     private final MockClock mMockClock = new MockClock();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 9251376..c58c92b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.os.BatteryConsumer;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -39,7 +38,6 @@
 import android.util.AtomicFile;
 import android.util.Log;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BatteryStatsHistory;
@@ -59,6 +57,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -80,13 +79,14 @@
     private BatteryStatsHistory.TraceDelegate mTracer;
     @Mock
     private BatteryStatsHistory.HistoryStepDetailsCalculator mStepDetailsCalculator;
+    @Mock
+    private BatteryStatsHistory.EventLogger mEventLogger;
     private List<String> mReadFiles = new ArrayList<>();
 
     @Before
-    public void setUp() {
+    public void setUp() throws IOException {
         MockitoAnnotations.initMocks(this);
-        Context context = InstrumentationRegistry.getContext();
-        mSystemDir = context.getDataDir();
+        mSystemDir = Files.createTempDirectory("BatteryStatsHistoryTest").toFile();
         mHistoryDir = new File(mSystemDir, "battery-history");
         String[] files = mHistoryDir.list();
         if (files != null) {
@@ -99,7 +99,7 @@
         mClock.realtime = 123;
 
         mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
-                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer);
+                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer, mEventLogger);
 
         when(mStepDetailsCalculator.getHistoryStepDetails())
                 .thenReturn(new BatteryStats.HistoryStepDetails());
@@ -238,7 +238,7 @@
 
         // create a new BatteryStatsHistory object, it will pick up existing history files.
         BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
-                null, mClock, mMonotonicClock, mTracer);
+                null, mClock, mMonotonicClock, mTracer, mEventLogger);
         // verify constructor can pick up all files from file system.
         verifyFileNames(history2, fileList);
         verifyActiveFile(history2, "33000.bh");
@@ -534,7 +534,7 @@
         // Keep the preserved part of history short - we only need to capture the very tail of
         // history.
         mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 1, 6000,
-                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer);
+                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer, mEventLogger);
 
         mHistory.forceRecordAllHistory();
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 8d51592..548fae7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -31,15 +31,18 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.UidTraffic;
 import android.content.Context;
+import android.hardware.SensorManager;
 import android.os.BatteryConsumer;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -51,11 +54,11 @@
 import android.os.Parcel;
 import android.os.WakeLockStats;
 import android.os.WorkSource;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 import android.view.Display;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.CpuScalingPolicies;
@@ -68,19 +71,26 @@
 import com.google.common.truth.LongSubject;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.time.Instant;
 import java.util.List;
 
-@LargeTest
 @RunWith(AndroidJUnit4.class)
 @SuppressWarnings("GuardedBy")
 public class BatteryStatsImplTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     @Mock
     private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader;
     @Mock
@@ -110,7 +120,7 @@
     private PowerStatsExporter mPowerStatsExporter;
 
     @Before
-    public void setUp() {
+    public void setUp() throws IOException {
         MockitoAnnotations.initMocks(this);
 
         when(mKernelUidCpuFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
@@ -128,8 +138,17 @@
                 .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader)
                 .setKernelWakelockReader(mKernelWakelockReader);
 
-        final Context context = InstrumentationRegistry.getContext();
-        File systemDir = context.getCacheDir();
+        File systemDir = Files.createTempDirectory("BatteryStatsHistoryTest").toFile();
+
+        Context context;
+        if (RavenwoodRule.isUnderRavenwood()) {
+            context = mock(Context.class);
+            SensorManager sensorManager = mock(SensorManager.class);
+            when(sensorManager.getSensorList(anyInt())).thenReturn(List.of());
+            when(context.getSystemService(SensorManager.class)).thenReturn(sensorManager);
+        } else {
+            context = InstrumentationRegistry.getContext();
+        }
         mPowerStatsStore = new PowerStatsStore(systemDir, mHandler,
                 new AggregatedPowerStatsConfig());
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerStatsExporter,
@@ -747,14 +766,22 @@
     }
 
     private UidTraffic createUidTraffic(int appUid, long rxBytes, long txBytes) {
-        final Parcel parcel = Parcel.obtain();
-        parcel.writeInt(appUid); // mAppUid
-        parcel.writeLong(rxBytes); // mRxBytes
-        parcel.writeLong(txBytes); // mTxBytes
-        parcel.setDataPosition(0);
-        UidTraffic uidTraffic = UidTraffic.CREATOR.createFromParcel(parcel);
-        parcel.recycle();
-        return uidTraffic;
+        if (RavenwoodRule.isUnderRavenwood()) {
+            UidTraffic uidTraffic = mock(UidTraffic.class);
+            when(uidTraffic.getUid()).thenReturn(appUid);
+            when(uidTraffic.getRxBytes()).thenReturn(rxBytes);
+            when(uidTraffic.getTxBytes()).thenReturn(txBytes);
+            return uidTraffic;
+        } else {
+            final Parcel parcel = Parcel.obtain();
+            parcel.writeInt(appUid); // mAppUid
+            parcel.writeLong(rxBytes); // mRxBytes
+            parcel.writeLong(txBytes); // mTxBytes
+            parcel.setDataPosition(0);
+            UidTraffic uidTraffic = UidTraffic.CREATOR.createFromParcel(parcel);
+            parcel.recycle();
+            return uidTraffic;
+        }
     }
 
     private BluetoothActivityEnergyInfo createBluetoothActivityEnergyInfo(
@@ -764,21 +791,31 @@
             long controllerIdleTimeMs,
             long controllerEnergyUsed,
             UidTraffic... uidTraffic) {
-        Parcel parcel = Parcel.obtain();
-        parcel.writeLong(timestamp); // mTimestamp
-        parcel.writeInt(
-                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE); // mBluetoothStackState
-        parcel.writeLong(controllerTxTimeMs); // mControllerTxTimeMs;
-        parcel.writeLong(controllerRxTimeMs); // mControllerRxTimeMs;
-        parcel.writeLong(controllerIdleTimeMs); // mControllerIdleTimeMs;
-        parcel.writeLong(controllerEnergyUsed); // mControllerEnergyUsed;
-        parcel.writeTypedList(ImmutableList.copyOf(uidTraffic)); // mUidTraffic
-        parcel.setDataPosition(0);
+        if (RavenwoodRule.isUnderRavenwood()) {
+            BluetoothActivityEnergyInfo info = mock(BluetoothActivityEnergyInfo.class);
+            when(info.getTimestampMillis()).thenReturn(timestamp);
+            when(info.getControllerTxTimeMillis()).thenReturn(controllerTxTimeMs);
+            when(info.getControllerRxTimeMillis()).thenReturn(controllerRxTimeMs);
+            when(info.getControllerIdleTimeMillis()).thenReturn(controllerIdleTimeMs);
+            when(info.getControllerEnergyUsed()).thenReturn(controllerEnergyUsed);
+            when(info.getUidTraffic()).thenReturn(ImmutableList.copyOf(uidTraffic));
+            return info;
+        } else {
+            Parcel parcel = Parcel.obtain();
+            parcel.writeLong(timestamp); // mTimestamp
+            parcel.writeInt(BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE);
+            parcel.writeLong(controllerTxTimeMs); // mControllerTxTimeMs;
+            parcel.writeLong(controllerRxTimeMs); // mControllerRxTimeMs;
+            parcel.writeLong(controllerIdleTimeMs); // mControllerIdleTimeMs;
+            parcel.writeLong(controllerEnergyUsed); // mControllerEnergyUsed;
+            parcel.writeTypedList(ImmutableList.copyOf(uidTraffic)); // mUidTraffic
+            parcel.setDataPosition(0);
 
-        BluetoothActivityEnergyInfo info =
-                BluetoothActivityEnergyInfo.CREATOR.createFromParcel(parcel);
-        parcel.recycle();
-        return info;
+            BluetoothActivityEnergyInfo info =
+                    BluetoothActivityEnergyInfo.CREATOR.createFromParcel(parcel);
+            parcel.recycle();
+            return info;
+        }
     }
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index eea2875..07cefa9 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -28,6 +28,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
 import android.app.ActivityManager;
@@ -40,6 +44,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.ActivityStatsTechSpecificInfo;
 import android.telephony.Annotation;
@@ -50,7 +55,6 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.Log;
-import android.util.MutableInt;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.view.Display;
@@ -63,8 +67,8 @@
 import com.android.internal.power.EnergyConsumerStats;
 import com.android.server.power.stats.BatteryStatsImpl.DualTimer;
 
-import junit.framework.TestCase;
-
+import org.junit.Rule;
+import org.junit.Test;
 import org.mockito.Mock;
 
 import java.util.ArrayList;
@@ -78,7 +82,14 @@
  * Test various BatteryStatsImpl noteStart methods.
  */
 @SuppressWarnings("GuardedBy")
-public class BatteryStatsNoteTest extends TestCase {
+@SmallTest
+public class BatteryStatsNoteTest {
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final String TAG = BatteryStatsNoteTest.class.getSimpleName();
 
     private static final int UID = 10500;
@@ -96,7 +107,7 @@
     /**
      * Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked.
      */
-    @SmallTest
+    @Test
     public void testNoteBluetoothScanResultLocked() throws Exception {
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClock());
         bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
@@ -125,7 +136,7 @@
     /**
      * Test BatteryStatsImpl.Uid.noteStartWakeLocked.
      */
-    @SmallTest
+    @Test
     public void testNoteStartWakeLocked() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -155,7 +166,7 @@
     /**
      * Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid.
      */
-    @SmallTest
+    @Test
     public void testNoteStartWakeLocked_isolatedUid() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
@@ -197,7 +208,7 @@
      * Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid, with a race where the
      * isolated uid is removed from batterystats before the wakelock has been stopped.
      */
-    @SmallTest
+    @Test
     public void testNoteStartWakeLocked_isolatedUidRace() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
@@ -241,7 +252,7 @@
     /**
      * Test BatteryStatsImpl.Uid.noteLongPartialWakelockStart for an isolated uid.
      */
-    @SmallTest
+    @Test
     public void testNoteLongPartialWakelockStart_isolatedUid() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
@@ -296,7 +307,7 @@
     /**
      * Test BatteryStatsImpl.Uid.noteLongPartialWakelockStart for an isolated uid.
      */
-    @SmallTest
+    @Test
     public void testNoteLongPartialWakelockStart_isolatedUidRace() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         PowerStatsUidResolver uidResolver = new PowerStatsUidResolver();
@@ -354,7 +365,7 @@
     /**
      * Test BatteryStatsImpl.noteUidProcessStateLocked.
      */
-    @SmallTest
+    @Test
     public void testNoteUidProcessStateLocked() throws Exception {
         final MockClock clocks = new MockClock();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -441,7 +452,7 @@
     /**
      * Test BatteryStatsImpl.updateTimeBasesLocked.
      */
-    @SmallTest
+    @Test
     public void testUpdateTimeBasesLocked() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -465,7 +476,7 @@
     /**
      * Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly.
      */
-    @SmallTest
+    @Test
     public void testNoteScreenStateLocked() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -512,7 +523,7 @@
      * Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly for
      * multi display devices
      */
-    @SmallTest
+    @Test
     public void testNoteScreenStateLocked_multiDisplay() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -606,7 +617,7 @@
      * Off             -------       ----------------------
      * Doze                                ----------------
      */
-    @SmallTest
+    @Test
     public void testNoteScreenStateTimersLocked() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -656,7 +667,7 @@
      * Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly for multi display
      * devices.
      */
-    @SmallTest
+    @Test
     public void testNoteScreenStateTimersLocked_multiDisplay() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -807,7 +818,7 @@
     /**
      * Test BatteryStatsImpl.noteScreenBrightnessLocked updates timers correctly.
      */
-    @SmallTest
+    @Test
     public void testScreenBrightnessLocked_multiDisplay() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -929,7 +940,7 @@
         checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
     }
 
-    @SmallTest
+    @Test
     public void testAlarmStartAndFinishLocked() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -967,7 +978,7 @@
         assertThat(iterator.next()).isNull();
     }
 
-    @SmallTest
+    @Test
     public void testAlarmStartAndFinishLocked_workSource() throws Exception {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -1013,7 +1024,7 @@
         assertEquals(500, item.eventTag.uid);
     }
 
-    @SmallTest
+    @Test
     public void testNoteWakupAlarmLocked() {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -1031,7 +1042,7 @@
         assertEquals(1, pkg.getWakeupAlarmStats().size());
     }
 
-    @SmallTest
+    @Test
     public void testNoteWakupAlarmLocked_workSource_uid() {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -1064,7 +1075,7 @@
         assertEquals(1, pkg.getWakeupAlarmStats().size());
     }
 
-    @SmallTest
+    @Test
     public void testNoteWakupAlarmLocked_workSource_workChain() {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -1090,7 +1101,7 @@
         assertEquals(0, pkg.getWakeupAlarmStats().size());
     }
 
-    @SmallTest
+    @Test
     public void testNoteGpsChanged() {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -1114,7 +1125,7 @@
         assertFalse(t.isRunningLocked());
     }
 
-    @SmallTest
+    @Test
     public void testNoteGpsChanged_workSource() {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -1138,7 +1149,7 @@
         assertFalse(t.isRunningLocked());
     }
 
-    @SmallTest
+    @Test
     public void testUpdateDisplayMeasuredEnergyStatsLocked() {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -1223,7 +1234,7 @@
         checkMeasuredCharge("H", uid1, blame1, uid2, blame2, globalDoze, bi);
     }
 
-    @SmallTest
+    @Test
     public void testUpdateCustomMeasuredEnergyStatsLocked_neverCalled() {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -1237,7 +1248,7 @@
         checkCustomBatteryConsumption("0", 0, 0, uid1, 0, 0, uid2, 0, 0, bi);
     }
 
-    @SmallTest
+    @Test
     public void testUpdateCustomMeasuredEnergyStatsLocked() {
         final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -1314,7 +1325,7 @@
                 "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
     }
 
-    @SmallTest
+    @Test
     public void testGetPerStateActiveRadioDurationMs_noModemActivity() {
         final MockClock clock = new MockClock(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
@@ -1470,7 +1481,7 @@
                 expectedTxDurationsMs, bi, state.currentTimeMs);
     }
 
-    @SmallTest
+    @Test
     public void testGetPerStateActiveRadioDurationMs_initialModemActivity() {
         final MockClock clock = new MockClock(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
@@ -1612,7 +1623,7 @@
                 expectedTxDurationsMs, bi, state.currentTimeMs);
     }
 
-    @SmallTest
+    @Test
     public void testGetPerStateActiveRadioDurationMs_withModemActivity() {
         final MockClock clock = new MockClock(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
@@ -1852,7 +1863,7 @@
                 expectedTxDurationsMs, bi, state.currentTimeMs);
     }
 
-    @SmallTest
+    @Test
     public void testGetPerStateActiveRadioDurationMs_withSpecificInfoModemActivity() {
         final MockClock clock = new MockClock(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
@@ -2145,19 +2156,19 @@
                 expectedTxDurationsMs, bi, state.currentTimeMs);
     }
 
-    @SmallTest
+    @Test
     @SuppressWarnings("GuardedBy")
     public void testProcStateSyncScheduling_mobileRadioActiveState() {
         final MockClock clock = new MockClock(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
-        final MutableInt lastProcStateChangeFlags = new MutableInt(0);
+        final int[] lastProcStateChangeFlags = new int[1];
 
         MockBatteryStatsImpl.DummyExternalStatsSync externalStatsSync =
                 new MockBatteryStatsImpl.DummyExternalStatsSync() {
                     @Override
                     public void scheduleSyncDueToProcessStateChange(int flags,
                             long delayMillis) {
-                        lastProcStateChangeFlags.value = flags;
+                        lastProcStateChangeFlags[0] = flags;
                     }
                 };
 
@@ -2170,19 +2181,19 @@
         bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
                 UID);
 
-        lastProcStateChangeFlags.value = 0;
+        lastProcStateChangeFlags[0] = 0;
         clock.realtime = clock.uptime = 2002;
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
 
         final int allProcFlags = BatteryStatsImpl.ExternalStatsSync.UPDATE_ON_PROC_STATE_CHANGE;
-        assertEquals(allProcFlags, lastProcStateChangeFlags.value);
+        assertEquals(allProcFlags, lastProcStateChangeFlags[0]);
 
         // Note mobile radio is off.
         curr = 1000L * (clock.realtime = clock.uptime = 3003);
         bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, curr,
                 UID);
 
-        lastProcStateChangeFlags.value = 0;
+        lastProcStateChangeFlags[0] = 0;
         clock.realtime = clock.uptime = 4004;
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
 
@@ -2190,10 +2201,10 @@
                 & ~BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO;
         assertEquals(
                 "An inactive radio should not be queried on proc state change",
-                noRadioProcFlags, lastProcStateChangeFlags.value);
+                noRadioProcFlags, lastProcStateChangeFlags[0]);
     }
 
-    @SmallTest
+    @Test
     public void testNoteMobileRadioPowerStateLocked() {
         long curr;
         boolean update;
@@ -2243,7 +2254,7 @@
                 update);
     }
 
-    @SmallTest
+    @Test
     public void testNoteMobileRadioPowerStateLocked_rateLimited() {
         long curr;
         boolean update;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java
index 9c70f37..96780c3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java
@@ -16,24 +16,36 @@
 
 package com.android.server.power.stats;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
 import android.app.ActivityManager;
 import android.os.BatteryStats;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
 
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
 
 /**
  * Test BatteryStatsImpl Sensor Timers.
  */
-public class BatteryStatsSensorTest extends TestCase {
+@SmallTest
+public class BatteryStatsSensorTest {
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
 
     private static final int UID = 10500;
     private static final int UID_2 = 10501; // second uid for testing pool usage
     private static final int SENSOR_ID = -10000;
 
-    @SmallTest
+    @Test
     public void testSensorStartStop() throws Exception {
         final MockClock clocks = new MockClock();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -70,7 +82,7 @@
                 clocks.realtime * 1000, BatteryStats.STATS_SINCE_CHARGED));
     }
 
-    @SmallTest
+    @Test
     public void testCountingWhileOffBattery() throws Exception {
         final MockClock clocks = new MockClock();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -106,7 +118,7 @@
         assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
     }
 
-    @SmallTest
+    @Test
     public void testCountingWhileOnBattery() throws Exception {
         final MockClock clocks = new MockClock();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -141,7 +153,7 @@
         assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
     }
 
-    @SmallTest
+    @Test
     public void testBatteryStatusOnToOff() throws Exception {
         final MockClock clocks = new MockClock();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -187,7 +199,7 @@
                 sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
     }
 
-    @SmallTest
+    @Test
     public void testBatteryStatusOffToOn() throws Exception {
         final MockClock clocks = new MockClock();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -238,7 +250,7 @@
         assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
     }
 
-    @SmallTest
+    @Test
     public void testPooledBackgroundUsage() throws Exception {
         final MockClock clocks = new MockClock();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -375,7 +387,7 @@
         assertEquals(2, bgTimer2.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
     }
 
-    @SmallTest
+    @Test
     public void testSensorReset() throws Exception {
         final MockClock clocks = new MockClock();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -419,7 +431,7 @@
         assertNull(sensor);
     }
 
-    @SmallTest
+    @Test
     public void testSensorResetTimes() throws Exception {
         final MockClock clocks = new MockClock();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsServTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsServTest.java
index 200eb1d..6f683ae 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsServTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsServTest.java
@@ -18,16 +18,25 @@
 
 import android.os.BatteryStats;
 import android.os.Parcel;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 
 import junit.framework.Assert;
-import junit.framework.TestCase;
+
+import org.junit.Rule;
+import org.junit.Test;
 
 /**
  * Provides test cases for android.os.BatteryStats.
  */
-public class BatteryStatsServTest extends TestCase {
+@SmallTest
+public class BatteryStatsServTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final String TAG = "BatteryStatsServTest";
 
     public static class TestServ extends BatteryStatsImpl.Uid.Pkg.Serv {
@@ -90,7 +99,7 @@
     /**
      * Test that the constructor and detach methods touch the time bast observer list.
      */
-    @SmallTest
+    @Test
     public void testConstructAndDetach() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
 
@@ -104,7 +113,7 @@
     /**
      * Test parceling and unparceling.
      */
-    @SmallTest
+    @Test
     public void testParceling() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
         TestServ orig = new TestServ(bsi);
@@ -133,7 +142,7 @@
     /**
      * Test getLaunchTimeToNow()
      */
-    @SmallTest
+    @Test
     public void testLaunchTimeToNow() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
         TestServ serv = new TestServ(bsi);
@@ -151,7 +160,7 @@
     /**
      * Test getStartTimeToNow()
      */
-    @SmallTest
+    @Test
     public void testStartTimeToNow() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
         TestServ serv = new TestServ(bsi);
@@ -168,7 +177,7 @@
     /**
      * Test startLaunchedLocked while not previously launched
      */
-    @SmallTest
+    @Test
     public void testStartLaunchedLockedWhileLaunched() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl() {
             @Override
@@ -197,7 +206,7 @@
     /**
      * Test startLaunchedLocked while previously launched
      */
-    @SmallTest
+    @Test
     public void testStartLaunchedLockedWhileNotLaunched() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl() {
             @Override
@@ -224,7 +233,7 @@
     /**
      * Test stopLaunchedLocked when not previously launched.
      */
-    @SmallTest
+    @Test
     public void testStopLaunchedLockedWhileNotLaunched() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl() {
             @Override
@@ -254,7 +263,7 @@
      * Test stopLaunchedLocked when previously launched, with measurable time between
      * start and stop.
      */
-    @SmallTest
+    @Test
     public void testStopLaunchedLockedWhileLaunchedNormal() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl() {
             @Override
@@ -283,7 +292,7 @@
      * Test stopLaunchedLocked when previously launched, with no measurable time between
      * start and stop.
      */
-    @SmallTest
+    @Test
     public void testStopLaunchedLockedWhileLaunchedTooQuick() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
         TestServ serv = new TestServ(bsi);
@@ -306,7 +315,7 @@
     /**
      * Test startRunningLocked while previously running
      */
-    @SmallTest
+    @Test
     public void testStartRunningLockedWhileRunning() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl() {
             @Override
@@ -335,7 +344,7 @@
     /**
      * Test startRunningLocked while not previously launched
      */
-    @SmallTest
+    @Test
     public void testStartRunningLockedWhileNotRunning() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl() {
             @Override
@@ -364,7 +373,7 @@
      * Test stopRunningLocked when previously launched, with measurable time between
      * start and stop.
      */
-    @SmallTest
+    @Test
     public void testStopRunningLockedWhileRunningNormal() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl() {
             @Override
@@ -393,7 +402,7 @@
      * Test stopRunningLocked when previously launched, with measurable time between
      * start and stop.
      */
-    @SmallTest
+    @Test
     public void testStopRunningLockedWhileRunningTooQuick() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
         TestServ serv = new TestServ(bsi);
@@ -416,7 +425,7 @@
     /**
      * Test that getBatteryStats returns the BatteryStatsImpl passed in to the contstructor.
      */
-    @SmallTest
+    @Test
     public void testGetBatteryStats() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
         TestServ serv = new TestServ(bsi);
@@ -427,7 +436,7 @@
     /**
      * Test getLaunches
      */
-    @SmallTest
+    @Test
     public void testGetLaunches() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
         TestServ serv = new TestServ(bsi);
@@ -449,7 +458,7 @@
     /**
      * Test getStartTime while running
      */
-    @SmallTest
+    @Test
     public void testGetStartTimeRunning() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
         TestServ serv = new TestServ(bsi);
@@ -475,7 +484,7 @@
     /**
      * Test getStartTime while not running
      */
-    @SmallTest
+    @Test
     public void testGetStartTimeNotRunning() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
         TestServ serv = new TestServ(bsi);
@@ -502,7 +511,7 @@
     /**
      * Test getStarts
      */
-    @SmallTest
+    @Test
     public void testGetStarts() throws Exception  {
         MockBatteryStatsImpl bsi = new MockBatteryStatsImpl();
         TestServ serv = new TestServ(bsi);
@@ -521,4 +530,3 @@
         Assert.assertEquals(8085, serv.getLaunches());
     }
 }
-
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
index face849..05d8a00 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
@@ -46,6 +46,7 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
[email protected]
 public class BatteryStatsUserLifecycleTests {
 
     private static final long POLL_INTERVAL_MS = 500;
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 2e0ba00..6cd79bc 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
@@ -23,6 +23,7 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
+import android.hardware.SensorManager;
 import android.os.BatteryConsumer;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -32,6 +33,7 @@
 import android.os.Parcel;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -40,36 +42,69 @@
 import com.android.internal.os.BatteryStatsHistoryIterator;
 import com.android.internal.os.PowerProfile;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.util.List;
-import java.util.Random;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BatteryUsageStatsProviderTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
     private static final long MINUTE_IN_MS = 60 * 1000;
     private static final double PRECISION = 0.00001;
 
-    private final File mHistoryDir = createTemporaryDirectory();
+    private File mHistoryDir;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule =
             new BatteryUsageStatsRule(12345, mHistoryDir)
                     .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
                     .setAveragePower(PowerProfile.POWER_AUDIO, 720.0);
+
     private MockClock mMockClock = mStatsRule.getMockClock();
+    private Context mContext;
+
+    @Before
+    public void setup() throws IOException {
+        mHistoryDir = Files.createTempDirectory("BatteryUsageStatsProviderTest").toFile();
+        clearDirectory(mHistoryDir);
+
+        if (RavenwoodRule.isUnderRavenwood()) {
+            mContext = mock(Context.class);
+            SensorManager sensorManager = mock(SensorManager.class);
+            when(mContext.getSystemService(SensorManager.class)).thenReturn(sensorManager);
+        } else {
+            mContext = InstrumentationRegistry.getContext();
+        }
+    }
+
+    private void clearDirectory(File dir) {
+        if (dir.exists()) {
+            for (File child : dir.listFiles()) {
+                if (child.isDirectory()) {
+                    clearDirectory(child);
+                }
+                child.delete();
+            }
+        }
+    }
 
     @Test
     public void test_getBatteryUsageStats() {
         BatteryStatsImpl batteryStats = prepareBatteryStats();
 
-        Context context = InstrumentationRegistry.getContext();
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
                 mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
@@ -105,8 +140,7 @@
     public void test_selectPowerComponents() {
         BatteryStatsImpl batteryStats = prepareBatteryStats();
 
-        Context context = InstrumentationRegistry.getContext();
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
                 mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
@@ -211,8 +245,7 @@
             batteryStats.noteAlarmFinishLocked("foo", null, APP_UID, 3_001_000, 2_001_000);
         }
 
-        Context context = InstrumentationRegistry.getContext();
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
                 mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
@@ -300,8 +333,7 @@
             }
         }
 
-        Context context = InstrumentationRegistry.getContext();
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
                 mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
@@ -311,7 +343,9 @@
         Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(batteryUsageStats, 0);
 
-        assertThat(parcel.dataSize()).isAtMost(128_000);
+        if (!RavenwoodRule.isUnderRavenwood()) {
+            assertThat(parcel.dataSize()).isAtMost(128_000);
+        }
 
         parcel.setDataPosition(0);
 
@@ -375,7 +409,6 @@
 
     @Test
     public void testAggregateBatteryStats() {
-        Context context = InstrumentationRegistry.getContext();
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
         setTime(5 * MINUTE_IN_MS);
@@ -384,11 +417,11 @@
         }
 
         PowerStatsStore powerStatsStore = new PowerStatsStore(
-                new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
+                new File(mHistoryDir, "powerstatsstore"),
                 mStatsRule.getHandler(), null);
         powerStatsStore.reset();
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
                 mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore,
                 mMockClock);
 
@@ -485,7 +518,6 @@
 
     @Test
     public void testAggregateBatteryStats_incompatibleSnapshot() {
-        Context context = InstrumentationRegistry.getContext();
         MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
         batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
 
@@ -511,7 +543,7 @@
         when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
                 .thenReturn(span1);
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, null,
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
                 mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore,
                 mMockClock);
 
@@ -523,20 +555,4 @@
                 .isEqualTo(batteryStats.getCustomEnergyConsumerNames());
         assertThat(stats.getStatsDuration()).isEqualTo(1234);
     }
-
-    private static final Random sRandom = new Random();
-
-    /**
-     * Creates a unique new temporary directory under "java.io.tmpdir".
-     */
-    private static File createTemporaryDirectory() {
-        while (true) {
-            String candidateName =
-                    BatteryUsageStatsProviderTest.class.getSimpleName() + sRandom.nextInt();
-            File result = new File(System.getProperty("java.io.tmpdir"), candidateName);
-            if (result.mkdir()) {
-                return result;
-            }
-        }
-    }
 }
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 ba2b538..8bdb029 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
@@ -49,6 +49,7 @@
 import java.io.File;
 import java.util.Arrays;
 
+@SuppressWarnings("SynchronizeOnNonFinalField")
 public class BatteryUsageStatsRule implements TestRule {
     public static final BatteryUsageStatsQuery POWER_PROFILE_MODEL_ONLY =
             new BatteryUsageStatsQuery.Builder()
@@ -71,6 +72,8 @@
     private int mDisplayCount = -1;
     private int mPerUidModemModel = -1;
     private NetworkStats mNetworkStats;
+    private boolean[] mSupportedStandardBuckets;
+    private String[] mCustomPowerComponentNames;
 
     public BatteryUsageStatsRule() {
         this(0, null);
@@ -102,6 +105,11 @@
         mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler);
         mBatteryStats.setPowerProfile(mPowerProfile);
         mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+        synchronized (mBatteryStats) {
+            mBatteryStats.initEnergyConsumerStatsLocked(mSupportedStandardBuckets,
+                    mCustomPowerComponentNames);
+        }
+        mBatteryStats.informThatAllExternalStatsAreFlushed();
 
         mBatteryStats.onSystemReady();
 
@@ -230,13 +238,15 @@
     /** Call only after setting the power profile information. */
     public BatteryUsageStatsRule initMeasuredEnergyStatsLocked(
             String[] customPowerComponentNames) {
-        final boolean[] supportedStandardBuckets =
-                new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
-        Arrays.fill(supportedStandardBuckets, true);
-        synchronized (mBatteryStats) {
-            mBatteryStats.initEnergyConsumerStatsLocked(supportedStandardBuckets,
-                    customPowerComponentNames);
-            mBatteryStats.informThatAllExternalStatsAreFlushed();
+        mCustomPowerComponentNames = customPowerComponentNames;
+        mSupportedStandardBuckets = new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
+        Arrays.fill(mSupportedStandardBuckets, true);
+        if (mBatteryStats != null) {
+            synchronized (mBatteryStats) {
+                mBatteryStats.initEnergyConsumerStatsLocked(mSupportedStandardBuckets,
+                        mCustomPowerComponentNames);
+                mBatteryStats.informThatAllExternalStatsAreFlushed();
+            }
         }
         return this;
     }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 079ea2c7..851cf4a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -37,6 +37,7 @@
 import android.os.Parcel;
 import android.os.UidBatteryConsumer;
 import android.os.UserBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Xml;
 
 import androidx.test.filters.SmallTest;
@@ -45,6 +46,7 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -62,6 +64,10 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BatteryUsageStatsTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
 
     private static final int USER_ID = 42;
     private static final int APP_UID1 = 271;
@@ -115,12 +121,13 @@
         final Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(outBatteryUsageStats, 0);
 
-        assertThat(parcel.dataSize()).isLessThan(2000);
+        // Under ravenwood this parcel is larger.  On a device, 2K would suffice
+        assertThat(parcel.dataSize()).isLessThan(128_000);
 
         parcel.setDataPosition(0);
 
         final BatteryUsageStats inBatteryUsageStats =
-                parcel.readParcelable(getClass().getClassLoader());
+                parcel.readParcelable(getClass().getClassLoader(), BatteryUsageStats.class);
         parcel.recycle();
 
         assertThat(inBatteryUsageStats.getUidBatteryConsumers()).hasSize(uidCount);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
index 4d4337c..fe6424f 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
@@ -18,6 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.UidTraffic;
@@ -28,6 +31,7 @@
 import android.os.Process;
 import android.os.UidBatteryConsumer;
 import android.os.WorkSource;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -46,10 +50,15 @@
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class BluetoothPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE, 10.0)
             .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX, 50.0)
@@ -331,33 +340,53 @@
         assertThat(usageDurationMillis).isEqualTo(durationMs);
     }
 
-    private UidTraffic createUidTraffic(int uid, long traffic1, long traffic2) {
-        final Parcel uidTrafficParcel = Parcel.obtain();
-        uidTrafficParcel.writeInt(uid);
-        uidTrafficParcel.writeLong(traffic1);
-        uidTrafficParcel.writeLong(traffic2);
-        uidTrafficParcel.setDataPosition(0);
+    private UidTraffic createUidTraffic(int appUid, long rxBytes, long txBytes) {
+        if (RavenwoodRule.isUnderRavenwood()) {
+            UidTraffic uidTraffic = mock(UidTraffic.class);
+            when(uidTraffic.getUid()).thenReturn(appUid);
+            when(uidTraffic.getRxBytes()).thenReturn(rxBytes);
+            when(uidTraffic.getTxBytes()).thenReturn(txBytes);
+            return uidTraffic;
+        } else {
+            final Parcel uidTrafficParcel = Parcel.obtain();
+            uidTrafficParcel.writeInt(appUid);
+            uidTrafficParcel.writeLong(rxBytes);
+            uidTrafficParcel.writeLong(txBytes);
+            uidTrafficParcel.setDataPosition(0);
 
-        UidTraffic traffic = UidTraffic.CREATOR.createFromParcel(uidTrafficParcel);
-        uidTrafficParcel.recycle();
-        return traffic;
+            UidTraffic traffic = UidTraffic.CREATOR.createFromParcel(uidTrafficParcel);
+            uidTrafficParcel.recycle();
+            return traffic;
+        }
     }
 
     private BluetoothActivityEnergyInfo createBtEnergyInfo(long timestamp, int stackState,
             long txTime, long rxTime, long idleTime, long energyUsed, List<UidTraffic> traffic) {
-        final Parcel btActivityEnergyInfoParcel = Parcel.obtain();
-        btActivityEnergyInfoParcel.writeLong(timestamp);
-        btActivityEnergyInfoParcel.writeInt(stackState);
-        btActivityEnergyInfoParcel.writeLong(txTime);
-        btActivityEnergyInfoParcel.writeLong(rxTime);
-        btActivityEnergyInfoParcel.writeLong(idleTime);
-        btActivityEnergyInfoParcel.writeLong(energyUsed);
-        btActivityEnergyInfoParcel.writeTypedList(traffic);
-        btActivityEnergyInfoParcel.setDataPosition(0);
+        if (RavenwoodRule.isUnderRavenwood()) {
+            BluetoothActivityEnergyInfo info = mock(BluetoothActivityEnergyInfo.class);
+            when(info.getTimestampMillis()).thenReturn(timestamp);
+            when(info.getBluetoothStackState()).thenReturn(stackState);
+            when(info.getControllerTxTimeMillis()).thenReturn(txTime);
+            when(info.getControllerRxTimeMillis()).thenReturn(rxTime);
+            when(info.getControllerIdleTimeMillis()).thenReturn(idleTime);
+            when(info.getControllerEnergyUsed()).thenReturn(energyUsed);
+            when(info.getUidTraffic()).thenReturn(ImmutableList.copyOf(traffic));
+            return info;
+        } else {
+            final Parcel btActivityEnergyInfoParcel = Parcel.obtain();
+            btActivityEnergyInfoParcel.writeLong(timestamp);
+            btActivityEnergyInfoParcel.writeInt(stackState);
+            btActivityEnergyInfoParcel.writeLong(txTime);
+            btActivityEnergyInfoParcel.writeLong(rxTime);
+            btActivityEnergyInfoParcel.writeLong(idleTime);
+            btActivityEnergyInfoParcel.writeLong(energyUsed);
+            btActivityEnergyInfoParcel.writeTypedList(traffic);
+            btActivityEnergyInfoParcel.setDataPosition(0);
 
-        BluetoothActivityEnergyInfo info = BluetoothActivityEnergyInfo.CREATOR
-                .createFromParcel(btActivityEnergyInfoParcel);
-        btActivityEnergyInfoParcel.recycle();
-        return info;
+            BluetoothActivityEnergyInfo info = BluetoothActivityEnergyInfo.CREATOR
+                    .createFromParcel(btActivityEnergyInfoParcel);
+            btActivityEnergyInfoParcel.recycle();
+            return info;
+        }
     }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
index ccace40..29e2f5e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
@@ -75,6 +75,7 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
[email protected]
 public class BstatsCpuTimesValidationTest {
     private static final String TAG = BstatsCpuTimesValidationTest.class.getSimpleName();
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
index 5fce32f0..7225f2d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
@@ -21,6 +21,7 @@
 import android.os.BatteryConsumer;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -34,12 +35,17 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CameraPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
     private static final int APP1_UID = Process.FIRST_APPLICATION_UID + 42;
     private static final int APP2_UID = Process.FIRST_APPLICATION_UID + 43;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_CAMERA, 360.0)
             .initMeasuredEnergyStatsLocked();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
index 993d834..5c0e268 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java
@@ -33,6 +33,7 @@
 
 import android.os.BatteryConsumer;
 import android.os.PersistableBundle;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.LongArray;
 
 import androidx.test.filters.SmallTest;
@@ -55,7 +56,12 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CpuAggregatedPowerStatsProcessorTest {
-    @Rule
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
             .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200})
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
index 888bc62..71a65c8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
@@ -32,6 +32,7 @@
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
@@ -55,6 +56,11 @@
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class CpuPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
     private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
@@ -62,7 +68,7 @@
 
     private static final int NUM_CPU_FREQS = 2 + 2;  // 2 clusters * 2 freqs each
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
             .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200})
@@ -93,14 +99,13 @@
     private SystemServerCpuThreadReader mMockSystemServerCpuThreadReader;
     @Mock
     private KernelSingleUidTimeReader mMockKernelSingleUidTimeReader;
+    private boolean[] mSupportedPowerBuckets;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        final boolean[] supportedPowerBuckets =
-                new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
-        supportedPowerBuckets[EnergyConsumerStats.POWER_BUCKET_CPU] = true;
+        mSupportedPowerBuckets = new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
 
         when(mMockCpuUidFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
 
@@ -112,8 +117,7 @@
                 .setKernelCpuUidUserSysTimeReader(mMockKernelCpuUidUserSysTimeReader)
                 .setKernelCpuUidActiveTimeReader(mMockKerneCpuUidActiveTimeReader)
                 .setKernelSingleUidTimeReader(mMockKernelSingleUidTimeReader)
-                .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader)
-                .initEnergyConsumerStatsLocked(supportedPowerBuckets, new String[0]);
+                .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader);
     }
 
     @Test
@@ -216,6 +220,10 @@
 
     @Test
     public void testMeasuredEnergyBasedModel() {
+        mSupportedPowerBuckets[EnergyConsumerStats.POWER_BUCKET_CPU] = true;
+        mStatsRule.getBatteryStats().initEnergyConsumerStatsLocked(mSupportedPowerBuckets,
+                new String[0]);
+
         when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
 
         when(mMockKernelCpuSpeedReaders[0].readDelta()).thenReturn(new long[]{1000, 2000});
@@ -397,6 +405,10 @@
 
     @Test
     public void testMeasuredEnergyBasedModel_perProcessState() {
+        mSupportedPowerBuckets[EnergyConsumerStats.POWER_BUCKET_CPU] = true;
+        mStatsRule.getBatteryStats().initEnergyConsumerStatsLocked(mSupportedPowerBuckets,
+                new String[0]);
+
         when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
 
         when(mMockKernelCpuSpeedReaders[0].readDelta()).thenReturn(new long[]{1000, 2000});
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
index 38a5d19..cbce7e8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
@@ -52,6 +52,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @LargeTest
[email protected]
 public class CpuPowerStatsCollectorValidationTest {
     @Rule
     public final CheckFlagsRule mCheckFlagsRule =
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
index 245faaf..4ab706e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
@@ -21,6 +21,7 @@
 import android.os.BatteryConsumer;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseLongArray;
 
 import androidx.test.filters.SmallTest;
@@ -34,11 +35,16 @@
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class CustomEnergyConsumerPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .initMeasuredEnergyStatsLocked(new String[]{"CUSTOM_COMPONENT1", "CUSTOM_COMPONENT2"});
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java
index 0f85fdc..757025e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java
@@ -21,6 +21,7 @@
 import android.os.BatteryConsumer;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -34,11 +35,16 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class FlashlightPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0);
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
index 3f2a6d0..3b5658c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
@@ -21,6 +21,7 @@
 import android.os.BatteryConsumer;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -35,12 +36,16 @@
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class GnssPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
     private static final double PRECISION = 0.00001;
 
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
     private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 222;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_GPS_ON, 360.0)
             .setAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java
index 3d150af..487d864 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.BatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -32,9 +33,14 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IdlePowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_CPU_IDLE, 720.0)
             .setAveragePower(PowerProfile.POWER_CPU_SUSPEND, 360.0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
index 2edfc8e..e023866 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
@@ -24,6 +24,7 @@
 
 import java.nio.charset.Charset;
 
[email protected]
 public class KernelWakelockReaderTest extends TestCase {
     /**
      * Helper class that builds the mock Kernel module file /d/wakeup_sources.
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java
index 2e962c3..1807ac5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java
@@ -24,7 +24,6 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.os.Parcel;
@@ -159,7 +158,7 @@
         // Test with detachIfReset=false
         mCounterArray.reset(false /* detachIfReset */);
         assertArrayEquals(ZEROES, mCounterArray.mCounts);
-        verifyZeroInteractions(mTimeBase);
+        verifyNoMoreInteractions(mTimeBase);
 
         updateCounts(COUNTS);
         // Test with detachIfReset=true
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterTest.java
index 0eac625..4b608e3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterTest.java
@@ -24,7 +24,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.os.Parcel;
@@ -140,7 +139,7 @@
         // Test with detachIfReset=false
         mCounter.reset(false /* detachIfReset */);
         assertEquals(0, getCount());
-        verifyZeroInteractions(mTimeBase);
+        verifyNoMoreInteractions(mTimeBase);
 
         mCounter.addCountLocked(COUNT, true);
         assertEquals(COUNT, getCount());
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java
index 2cce449..3a27188 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.BatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -32,9 +33,14 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MemoryPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_MEMORY, new double[] {360.0, 720.0, 1080.0});
 
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 78c4bac..9f06913 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
@@ -26,6 +26,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.Clock;
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.KernelCpuSpeedReader;
@@ -70,7 +71,9 @@
 
     MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler,
             PowerStatsUidResolver powerStatsUidResolver) {
-        super(clock, historyDirectory, handler, powerStatsUidResolver);
+        super(clock, historyDirectory, handler, powerStatsUidResolver,
+                mock(FrameworkStatsLogger.class), mock(BatteryStatsHistory.TraceDelegate.class),
+                mock(BatteryStatsHistory.EventLogger.class));
         initTimersAndCounters();
         setMaxHistoryBuffer(128 * 1024);
 
@@ -107,7 +110,9 @@
     }
 
     public Queue<UidToRemove> getPendingRemovedUids() {
-        return mPendingRemovedUids;
+        synchronized (this) {
+            return mPendingRemovedUids;
+        }
     }
 
     public boolean isOnBattery() {
@@ -275,6 +280,10 @@
         mHandler = handler;
     }
 
+    @Override
+    protected void updateBatteryPropertiesLocked() {
+    }
+
     public static class DummyExternalStatsSync implements ExternalStatsSync {
         public int flags = 0;
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index 22a7351..af5b462 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -60,7 +60,7 @@
     public void setup() throws ParseException {
         mHistory = new BatteryStatsHistory(32, 1024,
                 mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
-                mMonotonicClock, mock(BatteryStatsHistory.TraceDelegate.class));
+                mMonotonicClock, mock(BatteryStatsHistory.TraceDelegate.class), null);
 
         AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
         config.trackPowerComponent(TEST_POWER_COMPONENT)
@@ -178,7 +178,7 @@
     }
 
     @NonNull
-    private static CharSequence formatDateTime(long timeInMillis) {
+    private static String formatDateTime(long timeInMillis) {
         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
         format.getCalendar().setTimeZone(TimeZone.getTimeZone("GMT"));
         return format.format(new Date(timeInMillis));
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
index 3560a26..18d7b90 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
@@ -99,7 +99,7 @@
         mPowerStatsStore = new PowerStatsStore(storeDirectory, new TestHandler(), config);
         mHistory = new BatteryStatsHistory(Parcel.obtain(), storeDirectory, 0, 10000,
                 mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
-                mMonotonicClock, null);
+                mMonotonicClock, null, null);
         mPowerStatsAggregator = new PowerStatsAggregator(config, mHistory);
 
         mCpuStatsArrayLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
index 3723079..88d4ea7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
@@ -25,6 +25,7 @@
 import android.os.BatteryConsumer;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
@@ -38,14 +39,18 @@
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class ScreenPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
     private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
     private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 43;
     private static final long MINUTE_IN_MS = 60 * 1000;
     private static final long MINUTE_IN_US = 60 * 1000 * 1000;
-    private static final long HOUR_IN_MS = 60 * MINUTE_IN_MS;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0, 36.0)
             .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0, 48.0)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java
index 4745270..c01f05f 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java
@@ -27,6 +27,7 @@
 import android.os.BatteryConsumer;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -40,6 +41,11 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SensorPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
     private static final int SENSOR_HANDLE_1 = 1;
@@ -47,7 +53,7 @@
 
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
 
     @Test
@@ -60,10 +66,12 @@
                 .thenReturn(List.of(sensor1, sensor2));
 
         final BatteryStatsImpl stats = mStatsRule.getBatteryStats();
-        stats.noteStartSensorLocked(APP_UID, SENSOR_HANDLE_1, 1000, 1000);
-        stats.noteStopSensorLocked(APP_UID, SENSOR_HANDLE_1, 2000, 2000);
-        stats.noteStartSensorLocked(APP_UID, SENSOR_HANDLE_2, 3000, 3000);
-        stats.noteStopSensorLocked(APP_UID, SENSOR_HANDLE_2, 5000, 5000);
+        synchronized (stats) {
+            stats.noteStartSensorLocked(APP_UID, SENSOR_HANDLE_1, 1000, 1000);
+            stats.noteStopSensorLocked(APP_UID, SENSOR_HANDLE_1, 2000, 2000);
+            stats.noteStartSensorLocked(APP_UID, SENSOR_HANDLE_2, 3000, 3000);
+            stats.noteStopSensorLocked(APP_UID, SENSOR_HANDLE_2, 5000, 5000);
+        }
 
         SensorPowerCalculator calculator = new SensorPowerCalculator(sensorManager);
 
@@ -84,11 +92,20 @@
                 .isWithin(PRECISION).of(0.5);
     }
 
-    private Sensor createSensor(int handle, int type, double power) {
-        return new Sensor(new InputSensorInfo("name", "vendor", 0 /* version */,
-                handle, type, 100.0f /*maxRange */, 0.02f /* resolution */,
-                (float) power, 1000 /* minDelay */, 0 /* fifoReservedEventCount */,
-                0 /* fifoMaxEventCount */, "" /* stringType */, "" /* requiredPermission */,
-                0 /* maxDelay */, 0 /* flags */, 0 /* id */));
+    private Sensor createSensor(int handle, int type, float power) {
+        if (RavenwoodRule.isUnderRavenwood()) {
+            Sensor sensor = mock(Sensor.class);
+
+            when(sensor.getHandle()).thenReturn(handle);
+            when(sensor.getType()).thenReturn(type);
+            when(sensor.getPower()).thenReturn(power);
+            return sensor;
+        } else {
+            return new Sensor(new InputSensorInfo("name", "vendor", 0 /* version */,
+                    handle, type, 100.0f /*maxRange */, 0.02f /* resolution */,
+                    (float) power, 1000 /* minDelay */, 0 /* fifoReservedEventCount */,
+                    0 /* fifoMaxEventCount */, "" /* stringType */, "" /* requiredPermission */,
+                    0 /* maxDelay */, 0 /* flags */, 0 /* id */));
+        }
     }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/UserPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/UserPowerCalculatorTest.java
index f14745e..438f0ec 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/UserPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/UserPowerCalculatorTest.java
@@ -25,6 +25,7 @@
 import android.os.UidBatteryConsumer;
 import android.os.UserBatteryConsumer;
 import android.os.UserHandle;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -36,6 +37,11 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class UserPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     public static final int USER1 = 0;
     public static final int USER2 = 1625;
 
@@ -43,7 +49,7 @@
     private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 272;
     private static final int APP_UID3 = Process.FIRST_APPLICATION_UID + 314;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java
index f578aa3..b9b7101 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java
@@ -21,6 +21,7 @@
 import android.os.BatteryConsumer;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -34,11 +35,16 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VideoPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_VIDEO, 360.0);
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java
index f196185..5b7762d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java
@@ -23,6 +23,7 @@
 import android.os.Process;
 import android.os.UidBatteryConsumer;
 import android.os.WorkSource;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -36,12 +37,17 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class WakelockPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
     private static final int APP_PID = 3145;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_CPU_IDLE, 360.0);
 
@@ -51,10 +57,12 @@
 
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
-        batteryStats.noteStartWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "",
-                BatteryStats.WAKE_TYPE_PARTIAL, true, 1000, 1000);
-        batteryStats.noteStopWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "",
-                BatteryStats.WAKE_TYPE_PARTIAL, 2000, 2000);
+        synchronized (batteryStats) {
+            batteryStats.noteStartWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake",
+                    "", BatteryStats.WAKE_TYPE_PARTIAL, true, 1000, 1000);
+            batteryStats.noteStopWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake",
+                    "", BatteryStats.WAKE_TYPE_PARTIAL, 2000, 2000);
+        }
 
         mStatsRule.setTime(10_000, 6_000);
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
index 113be8b..8e221be 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
@@ -23,6 +23,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import android.app.usage.NetworkStatsManager;
 import android.net.NetworkCapabilities;
 import android.net.NetworkStats;
@@ -33,6 +36,7 @@
 import android.os.UidBatteryConsumer;
 import android.os.WorkSource;
 import android.os.connectivity.WifiActivityEnergyInfo;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -44,10 +48,17 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class WifiPowerCalculatorTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private static final double PRECISION = 0.00001;
 
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
@@ -55,7 +66,7 @@
     @Mock
     NetworkStatsManager mNetworkStatsManager;
 
-    @Rule
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE, 360.0)
             .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX, 480.0)
@@ -64,6 +75,7 @@
             .setAveragePower(PowerProfile.POWER_WIFI_SCAN, 480.0)
             .setAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, 720.0)
             .setAveragePower(PowerProfile.POWER_WIFI_ACTIVE, 1080.0)
+            .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE, 3700)
             .initMeasuredEnergyStatsLocked();
 
     /** Sets up a batterystats object with pre-populated network values. */
@@ -78,21 +90,54 @@
         return batteryStats;
     }
 
-    private NetworkStats buildNetworkStats(long elapsedRealtime, int rxBytes, int rxPackets,
-            int txBytes, int txPackets) {
-        return new NetworkStats(elapsedRealtime, 1)
-                .addEntry(new NetworkStats.Entry("wifi", APP_UID, 0, 0,
-                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, rxPackets,
-                        txBytes, txPackets, 100))
-                .addEntry(new NetworkStats.Entry("wifi", Process.WIFI_UID, 0, 0,
-                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1111, 111,
-                        2222, 22, 111));
+    private NetworkStats buildNetworkStats(long elapsedRealtime, long rxBytes, long rxPackets,
+            long txBytes, long txPackets) {
+        if (RavenwoodRule.isUnderRavenwood()) {
+            NetworkStats stats = mock(NetworkStats.class);
+//        when(stats.getElapsedRealtime()).thenReturn(elapsedRealtime);
+
+            NetworkStats.Entry entry1 = mock(NetworkStats.Entry.class);
+//        when(entry1.getIface()).thenReturn("wifi");
+            when(entry1.getUid()).thenReturn(APP_UID);
+            when(entry1.getMetered()).thenReturn(METERED_NO);
+            when(entry1.getRoaming()).thenReturn(ROAMING_NO);
+            when(entry1.getDefaultNetwork()).thenReturn(DEFAULT_NETWORK_NO);
+            when(entry1.getRxBytes()).thenReturn(rxBytes);
+            when(entry1.getRxPackets()).thenReturn(rxPackets);
+            when(entry1.getTxBytes()).thenReturn(txBytes);
+            when(entry1.getTxPackets()).thenReturn(txPackets);
+            when(entry1.getOperations()).thenReturn(100L);
+
+            NetworkStats.Entry entry2 = mock(NetworkStats.Entry.class);
+//        when(entry2.getIface()).thenReturn("wifi");
+            when(entry2.getUid()).thenReturn(Process.WIFI_UID);
+            when(entry2.getMetered()).thenReturn(METERED_NO);
+            when(entry2.getRoaming()).thenReturn(ROAMING_NO);
+            when(entry2.getDefaultNetwork()).thenReturn(DEFAULT_NETWORK_NO);
+            when(entry2.getRxBytes()).thenReturn(1111L);
+            when(entry2.getRxPackets()).thenReturn(111L);
+            when(entry2.getTxBytes()).thenReturn(2222L);
+            when(entry2.getTxPackets()).thenReturn(22L);
+            when(entry2.getOperations()).thenReturn(111L);
+
+            when(stats.iterator()).thenAnswer(inv->List.of(entry1, entry2).iterator());
+
+            return stats;
+        } else {
+            return new NetworkStats(elapsedRealtime, 1)
+                    .addEntry(new NetworkStats.Entry("wifi", APP_UID, 0, 0,
+                            METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, rxPackets,
+                            txBytes, txPackets, 100))
+                    .addEntry(new NetworkStats.Entry("wifi", Process.WIFI_UID, 0, 0,
+                            METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1111, 111,
+                            2222, 22, 111));
+        }
     }
 
     /** Sets up an WifiActivityEnergyInfo for ActivityController-model-based tests. */
     private WifiActivityEnergyInfo setupPowerControllerBasedModelEnergyNumbersInfo() {
-        return new WifiActivityEnergyInfo(10000,
-                WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000);
+        return buildWifiActivityEnergyInfo(10000L, WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE,
+                1000L, 2000L, 3000L, 4000L);
     }
 
     @Test
@@ -142,7 +187,7 @@
         uid.setProcessStateForTest(
                 BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000);
 
-        batteryStats.updateWifiState(new WifiActivityEnergyInfo(2000,
+        batteryStats.updateWifiState(buildWifiActivityEnergyInfo(2000,
                         WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000),
                 POWER_DATA_UNAVAILABLE, 2000, 2000,
                 mNetworkStatsManager);
@@ -152,7 +197,7 @@
 
         mStatsRule.setNetworkStats(buildNetworkStats(4000, 5000, 200, 7000, 80));
 
-        batteryStats.updateWifiState(new WifiActivityEnergyInfo(4000,
+        batteryStats.updateWifiState(buildWifiActivityEnergyInfo(4000,
                         WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000),
                 POWER_DATA_UNAVAILABLE, 4000, 4000,
                 mNetworkStatsManager);
@@ -231,7 +276,7 @@
         uid.setProcessStateForTest(
                 BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000);
 
-        batteryStats.updateWifiState(new WifiActivityEnergyInfo(2000,
+        batteryStats.updateWifiState(buildWifiActivityEnergyInfo(2000,
                         WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000),
                 1_000_000, 2000, 2000,
                 mNetworkStatsManager);
@@ -241,7 +286,7 @@
 
         mStatsRule.setNetworkStats(buildNetworkStats(4000, 5000, 200, 7000, 80));
 
-        batteryStats.updateWifiState(new WifiActivityEnergyInfo(4000,
+        batteryStats.updateWifiState(buildWifiActivityEnergyInfo(4000,
                         WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000),
                 5_000_000, 4000, 4000,
                 mNetworkStatsManager);
@@ -329,4 +374,43 @@
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
+
+    private WifiActivityEnergyInfo buildWifiActivityEnergyInfo(long timeSinceBoot,
+            int stackState, long txDuration, long rxDuration, long scanDuration,
+            long idleDuration) {
+        if (RavenwoodRule.isUnderRavenwood()) {
+            WifiActivityEnergyInfo info = mock(WifiActivityEnergyInfo.class);
+            when(info.getTimeSinceBootMillis()).thenReturn(timeSinceBoot);
+            when(info.getStackState()).thenReturn(stackState);
+            when(info.getControllerTxDurationMillis()).thenReturn(txDuration);
+            when(info.getControllerRxDurationMillis()).thenReturn(rxDuration);
+            when(info.getControllerScanDurationMillis()).thenReturn(scanDuration);
+            when(info.getControllerIdleDurationMillis()).thenReturn(idleDuration);
+            long energy = calculateEnergyMicroJoules(txDuration, rxDuration, idleDuration);
+            when(info.getControllerEnergyUsedMicroJoules()).thenReturn(energy);
+            return info;
+        } else {
+            return new WifiActivityEnergyInfo(timeSinceBoot, stackState, txDuration, rxDuration,
+                    scanDuration, idleDuration);
+        }
+    }
+
+    // See WifiActivityEnergyInfo
+    private long calculateEnergyMicroJoules(
+            long txDurationMillis, long rxDurationMillis, long idleDurationMillis) {
+        PowerProfile powerProfile = mStatsRule.getPowerProfile();
+        final double idleCurrent = powerProfile.getAveragePower(
+                PowerProfile.POWER_WIFI_CONTROLLER_IDLE);
+        final double rxCurrent = powerProfile.getAveragePower(
+                PowerProfile.POWER_WIFI_CONTROLLER_RX);
+        final double txCurrent = powerProfile.getAveragePower(
+                PowerProfile.POWER_WIFI_CONTROLLER_TX);
+        final double voltage = powerProfile.getAveragePower(
+                PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
+
+        return (long) ((txDurationMillis * txCurrent
+                + rxDurationMillis * rxCurrent
+                + idleDurationMillis * idleCurrent)
+                * voltage);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 2f0257a..ef80b59 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.accessibility;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -24,6 +26,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -88,7 +91,7 @@
 
     @Mock AccessibilityUserState mMockUserState;
     @Mock Context mMockContext;
-    @Mock AccessibilityServiceInfo mMockServiceInfo;
+    AccessibilityServiceInfo mServiceInfo;
     @Mock ResolveInfo mMockResolveInfo;
     @Mock AccessibilitySecurityPolicy mMockSecurityPolicy;
     @Mock AccessibilityWindowManager mMockA11yWindowManager;
@@ -115,7 +118,8 @@
         when(mMockSystemSupport.getMotionEventInjectorForDisplayLocked(
                 Display.DEFAULT_DISPLAY)).thenReturn(mMockMotionEventInjector);
 
-        when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
+        mServiceInfo = spy(new AccessibilityServiceInfo());
+        when(mServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
         mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
         mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
 
@@ -125,7 +129,7 @@
                 .thenReturn(new DisplayManager(mMockContext));
 
         mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext,
-                COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, mHandler, new Object(),
+                COMPONENT_NAME, mServiceInfo, SERVICE_ID, mHandler, new Object(),
                 mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
                 mMockWindowManagerInternal, mMockSystemActionPerformer,
                 mMockA11yWindowManager, mMockActivityTaskManagerInternal);
@@ -286,4 +290,22 @@
         verify(mMockMagnificationProcessor).resetAllIfNeeded(anyInt());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_RESETTABLE_DYNAMIC_PROPERTIES)
+    public void binderDied_resetA11yServiceInfo() {
+        final int flag = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
+        setServiceBinding(COMPONENT_NAME);
+        mConnection.bindLocked();
+        mConnection.onServiceConnected(COMPONENT_NAME, mMockIBinder);
+        AccessibilityServiceInfo info = mConnection.getServiceInfo();
+        assertThat(info.flags & flag).isEqualTo(0);
+
+        info = mConnection.getServiceInfo();
+        info.flags |= flag;
+        mConnection.setServiceInfo(info);
+        assertThat(mConnection.getServiceInfo().flags & flag).isEqualTo(flag);
+
+        mConnection.binderDied();
+        assertThat(mConnection.getServiceInfo().flags & flag).isEqualTo(0);
+    }
 }
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 77be01c..96ffec1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -303,6 +303,7 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ClassRule;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -8410,6 +8411,27 @@
     }
 
     @Test
+    public void testOnNotificationActionClickLifetimeExtendedEnds() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
+        final Notification.Action action =
+                new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
+                        mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+        // Creates a notification marked as being lifetime extended.
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+        mService.addNotification(r);
+        // Call on action click.
+        NotificationVisibility notificationVisibility =
+                NotificationVisibility.obtain(r.getKey(), 1, 2, true);
+        mService.mNotificationDelegate.onNotificationActionClick(
+                10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility,
+                /*generatedByAssistant=*/false);
+        // The flag is removed, so the notification is no longer lifetime extended.
+        assertThat(r.getSbn().getNotification().flags
+                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0);
+    }
+
+    @Test
     public void testOnAssistantNotificationActionClick() {
         final int actionIndex = 1;
         final Notification.Action action =
@@ -14291,6 +14313,7 @@
     }
 
     @Test
+    @Ignore("b/324348078")
     public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
                 .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
@@ -14380,6 +14403,7 @@
     }
 
     @Test
+    @Ignore("b/324348078")
     public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
                 .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 7462201..0d88b98d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -23,6 +23,7 @@
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
@@ -161,6 +162,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.truth.Correspondence;
+import com.google.common.util.concurrent.SettableFuture;
 import com.google.protobuf.InvalidProtocolBufferException;
 import com.google.testing.junit.testparameterinjector.TestParameter;
 import com.google.testing.junit.testparameterinjector.TestParameterInjector;
@@ -5222,6 +5224,52 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void testCallbacks_policy() throws Exception {
+        setupZenConfig();
+        assertThat(mZenModeHelper.getNotificationPolicy().allowReminders()).isTrue();
+        SettableFuture<Policy> futurePolicy = SettableFuture.create();
+        mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
+            @Override
+            void onPolicyChanged(Policy newPolicy) {
+                futurePolicy.set(newPolicy);
+            }
+        });
+
+        Policy totalSilencePolicy = new Policy(0, 0, 0);
+        mZenModeHelper.setNotificationPolicy(totalSilencePolicy, UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+        Policy callbackPolicy = futurePolicy.get(1, TimeUnit.SECONDS);
+        assertThat(callbackPolicy.allowReminders()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void testCallbacks_consolidatedPolicy() throws Exception {
+        setupZenConfig();
+        assertThat(mZenModeHelper.getConsolidatedNotificationPolicy().allowAlarms()).isTrue();
+        SettableFuture<Policy> futureConsolidatedPolicy = SettableFuture.create();
+        mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
+            @Override
+            void onConsolidatedPolicyChanged(Policy newConsolidatedPolicy) {
+                futureConsolidatedPolicy.set(newConsolidatedPolicy);
+            }
+        });
+
+        String totalSilenceRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setOwner(OWNER)
+                        .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+                        .build(),
+                UPDATE_ORIGIN_APP, "reasons", 0);
+        mZenModeHelper.setAutomaticZenRuleState(totalSilenceRuleId,
+                new Condition(CONDITION_ID, "", STATE_TRUE), UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+        Policy callbackPolicy = futureConsolidatedPolicy.get(1, TimeUnit.SECONDS);
+        assertThat(callbackPolicy.allowAlarms()).isFalse();
+    }
+
+    @Test
     public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.mConfig.automaticRules.clear();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
index a8b2178..bc83b19 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -27,7 +27,6 @@
 import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.BuildUtils;
@@ -43,6 +42,7 @@
 import android.view.WindowlessWindowManager;
 import android.widget.Button;
 import android.widget.FrameLayout;
+import android.window.InputTransferToken;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
@@ -110,7 +110,7 @@
         mInstrumentation.runOnMainSync(() -> {
             TestWindowlessWindowManager wwm = new TestWindowlessWindowManager(
                     mActivity.getResources().getConfiguration(), sc,
-                    mSurfaceView.getHostToken());
+                    mSurfaceView.getViewRootImpl().getInputTransferToken());
 
             mScvh1 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
                     wwm, "requestFocusWithMultipleWindows");
@@ -151,8 +151,8 @@
         private final SurfaceControl mRoot;
 
         TestWindowlessWindowManager(Configuration c, SurfaceControl rootSurface,
-                IBinder hostInputToken) {
-            super(c, rootSurface, hostInputToken);
+                InputTransferToken inputTransferToken) {
+            super(c, rootSurface, inputTransferToken);
             mRoot = rootSurface;
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 7d8eb90..ce890f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -33,6 +33,7 @@
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_CONFIG_AT_END;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
@@ -2540,6 +2541,36 @@
     }
 
     @Test
+    public void testConfigAtEnd() {
+        final TransitionController controller = mDisplayContent.mTransitionController;
+        Transition transit = createTestTransition(TRANSIT_CHANGE, controller);
+        final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+        final Task task = createTask(mDisplayContent);
+        final Rect taskBounds = new Rect(0, 0, 200, 300);
+        task.getConfiguration().windowConfiguration.setBounds(taskBounds);
+        final ActivityRecord activity = createActivityRecord(task);
+        activity.setVisibleRequested(true);
+        activity.setVisible(true);
+
+        controller.moveToCollecting(transit);
+        transit.collect(task);
+        transit.setConfigAtEnd(task);
+        task.getRequestedOverrideConfiguration().windowConfiguration.setBounds(
+                new Rect(10, 10, 200, 300));
+        task.onRequestedOverrideConfigurationChanged(task.getRequestedOverrideConfiguration());
+
+        controller.requestStartTransition(transit, task, null, null);
+        player.start();
+        assertTrue(activity.isConfigurationDispatchPaused());
+        // config-at-end flag must propagate up to task if activity was promoted.
+        assertTrue(player.mLastReady.getChange(
+                task.mRemoteToken.toWindowContainerToken()).hasFlags(FLAG_CONFIG_AT_END));
+        player.finish();
+        assertFalse(activity.isConfigurationDispatchPaused());
+    }
+
+    @Test
     public void testReadyTrackerBasics() {
         final TransitionController controller = new TestTransitionController(
                 mock(ActivityTaskManagerService.class));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 06afa38..4da519c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -94,6 +94,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.window.ClientWindowFrames;
+import android.window.InputTransferToken;
 import android.window.ScreenCapture;
 import android.window.WindowContainerToken;
 
@@ -964,14 +965,14 @@
         final int callingPid = 1234;
         final SurfaceControl surfaceControl = mock(SurfaceControl.class);
         final IBinder window = new Binder();
-        final IBinder focusGrantToken = mock(IBinder.class);
+        final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
 
         final InputChannel inputChannel = new InputChannel();
         assertThrows(IllegalArgumentException.class, () ->
                 mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY,
                         surfaceControl, window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE,
                         PRIVATE_FLAG_TRUSTED_OVERLAY, INPUT_FEATURE_SPY, TYPE_APPLICATION,
-                        null /* windowToken */, focusGrantToken, "TestInputChannel",
+                        null /* windowToken */, inputTransferToken, "TestInputChannel",
                         inputChannel));
     }
 
@@ -982,12 +983,12 @@
         final int callingPid = 1234;
         final SurfaceControl surfaceControl = mock(SurfaceControl.class);
         final IBinder window = new Binder();
-        final IBinder focusGrantToken = mock(IBinder.class);
+        final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
 
         final InputChannel inputChannel = new InputChannel();
         mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
                 window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY,
-                INPUT_FEATURE_SPY, TYPE_APPLICATION, null /* windowToken */, focusGrantToken,
+                INPUT_FEATURE_SPY, TYPE_APPLICATION, null /* windowToken */, inputTransferToken,
                 "TestInputChannel", inputChannel);
 
         verify(mTransaction).setInputWindowInfo(
@@ -1002,12 +1003,12 @@
         final int callingPid = 1234;
         final SurfaceControl surfaceControl = mock(SurfaceControl.class);
         final IBinder window = new Binder();
-        final IBinder focusGrantToken = mock(IBinder.class);
+        final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
 
         final InputChannel inputChannel = new InputChannel();
         mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
                 window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY,
-                0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, focusGrantToken,
+                0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, inputTransferToken,
                 "TestInputChannel", inputChannel);
         verify(mTransaction).setInputWindowInfo(
                 eq(surfaceControl),
@@ -1026,12 +1027,12 @@
         final int callingPid = 1234;
         final SurfaceControl surfaceControl = mock(SurfaceControl.class);
         final IBinder window = new Binder();
-        final IBinder focusGrantToken = mock(IBinder.class);
+        final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
 
         final InputChannel inputChannel = new InputChannel();
         mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
                 window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY,
-                0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, focusGrantToken,
+                0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, inputTransferToken,
                 "TestInputChannel", inputChannel);
         verify(mTransaction).setInputWindowInfo(
                 eq(surfaceControl),
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 711be02..9441fb5 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -501,4 +501,22 @@
      *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      */
      void stopSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
+
+     /**
+      * Abort all outgoing satellite datagrams which vendor service has received from Telephony
+      * framework.
+      *
+      * This API helps modem to be in sync with framework when framework times out on sending
+      * datagrams.
+      *
+      * @param resultCallback The callback to receive the error code result of the operation.
+      *
+      * Valid result codes returned:
+      *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+      *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+      *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
+      *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+      *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+      */
+     void abortSendingSatelliteDatagrams(in IIntegerConsumer resultCallback);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index abacd15..f17ff17 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -265,6 +265,14 @@
                     "stopSendingNtnSignalStrength");
         }
 
+        @Override
+        public void abortSendingSatelliteDatagrams(IIntegerConsumer resultCallback)
+                throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this.abortSendingSatelliteDatagrams(resultCallback),
+                    "abortSendingSatelliteDatagrams");
+        }
+
         // Call the methods with a clean calling identity on the executor and wait indefinitely for
         // the future to return.
         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -783,4 +791,13 @@
     public void stopSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback){
         // stub implementation
     }
+
+    /**
+     * Requests to abort sending satellite datagrams
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     */
+    public void abortSendingSatelliteDatagrams(@NonNull IIntegerConsumer resultCallback){
+        // stub implementation
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index bd47b1f..ff2ee27 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3046,13 +3046,24 @@
     boolean setSatellitePointingUiClassName(in String packageName, in String className);
 
     /**
-     * This API can be used by only CTS to update the timeout duration in milliseconds whether
-     * the device is aligned with the satellite for demo mode
+     * This API can be used by only CTS to override the timeout durations used by the
+     * DatagramController module.
      *
      * @param timeoutMillis The timeout duration in millisecond.
      * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
      */
-    boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis);
+    boolean setDatagramControllerTimeoutDuration(
+            boolean reset, int timeoutType, long timeoutMillis);
+
+    /**
+     * This API can be used by only CTS to override the timeout durations used by the
+     * SatelliteController module.
+     *
+     * @param timeoutMillis The timeout duration in millisecond.
+     * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+     */
+    boolean setSatelliteControllerTimeoutDuration(
+            boolean reset, int timeoutType, long timeoutMillis);
 
     /**
      * This API can be used in only testing to override connectivity status in monitoring emergency
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java
index 631fc02..eba9910 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java
@@ -17,6 +17,7 @@
 
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
+import android.os.Parcel;
 import android.util.Base64;
 
 import java.text.DecimalFormat;
@@ -31,6 +32,7 @@
     private static final HashMap<Long, CursorWindow_host> sInstances = new HashMap<>();
     private static long sNextId = 1;
 
+    private String mName;
     private int mColumnNum;
     private static class Row {
         String[] fields;
@@ -41,6 +43,7 @@
 
     public static long nativeCreate(String name, int cursorWindowSize) {
         CursorWindow_host instance = new CursorWindow_host();
+        instance.mName = name;
         long instanceId = sNextId++;
         sInstances.put(instanceId, instance);
         return instanceId;
@@ -50,6 +53,10 @@
         sInstances.remove(windowPtr);
     }
 
+    public static String nativeGetName(long windowPtr) {
+        return sInstances.get(windowPtr).mName;
+    }
+
     public static boolean nativeSetNumColumns(long windowPtr, int columnNum) {
         sInstances.get(windowPtr).mColumnNum = columnNum;
         return true;
@@ -156,4 +163,30 @@
                 return null;
         }
     }
+
+    public static void nativeWriteToParcel(long windowPtr, Parcel parcel) {
+        CursorWindow_host window = sInstances.get(windowPtr);
+        parcel.writeString(window.mName);
+        parcel.writeInt(window.mColumnNum);
+        parcel.writeInt(window.mRows.size());
+        for (int row = 0; row < window.mRows.size(); row++) {
+            parcel.writeStringArray(window.mRows.get(row).fields);
+            parcel.writeIntArray(window.mRows.get(row).types);
+        }
+    }
+
+    public static long nativeCreateFromParcel(Parcel parcel) {
+        long windowPtr = nativeCreate(null, 0);
+        CursorWindow_host window = sInstances.get(windowPtr);
+        window.mName = parcel.readString();
+        window.mColumnNum = parcel.readInt();
+        int rowCount = parcel.readInt();
+        for (int row = 0; row < rowCount; row++) {
+            Row r = new Row();
+            r.fields = parcel.createStringArray();
+            r.types = parcel.createIntArray();
+            window.mRows.add(r);
+        }
+        return windowPtr;
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
index a135623..4d39d88 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
@@ -143,6 +143,16 @@
             updateValue(values, timestampMs);
         }
 
+        public void addCounts(long[] delta) {
+            if (!mEnabled) {
+                return;
+            }
+
+            for (int i = 0; i < mArrayLength; i++) {
+                mStates[mCurrentState].mCounter[i] += delta[i];
+            }
+        }
+
         public void getValues(long[] values, int state) {
             System.arraycopy(mStates[state].mCounter, 0, values, 0, mArrayLength);
         }
@@ -331,6 +341,10 @@
                 LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
     }
 
+    public static void native_addCounts(long instanceId, long containerInstanceId) {
+        getInstance(instanceId).addCounts(LongArrayContainer_host.getInstance(containerInstanceId));
+    }
+
     public static void native_getCounts(long instanceId, long containerInstanceId, int state) {
         getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId),
                 state);
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongMultiStateCounter_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongMultiStateCounter_host.java
new file mode 100644
index 0000000..a5d0fc6
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongMultiStateCounter_host.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.hoststubgen.nativesubstitution;
+
+import android.os.BadParcelableException;
+import android.os.Parcel;
+
+import java.util.HashMap;
+
+/**
+ * Native implementation substitutions for the LongMultiStateCounter class.
+ */
+public class LongMultiStateCounter_host {
+
+    /**
+     * A reimplementation of {@link com.android.internal.os.LongMultiStateCounter}, only in
+     * Java instead of native.  The majority of the code (in C++) can be found in
+     * /frameworks/native/libs/battery/MultiStateCounter.h
+     */
+    private static class LongMultiStateCounterRavenwood {
+        private final int mStateCount;
+        private int mCurrentState;
+        private long mLastStateChangeTimestampMs = -1;
+        private long mLastUpdateTimestampMs = -1;
+        private boolean mEnabled = true;
+
+        private static class State {
+            private long mTimeInStateSinceUpdate;
+            private long mCounter;
+        }
+
+        private final State[] mStates;
+        private long mValue;
+
+        LongMultiStateCounterRavenwood(int stateCount) {
+            mStateCount = stateCount;
+            mStates = new State[stateCount];
+            for (int i = 0; i < mStateCount; i++) {
+                mStates[i] = new State();
+            }
+        }
+
+        public void setEnabled(boolean enabled, long timestampMs) {
+            if (enabled == mEnabled) {
+                return;
+            }
+
+            if (!enabled) {
+                setState(mCurrentState, timestampMs);
+                mEnabled = false;
+            } else {
+                if (timestampMs < mLastUpdateTimestampMs) {
+                    timestampMs = mLastUpdateTimestampMs;
+                }
+
+                if (mLastStateChangeTimestampMs >= 0) {
+                    mLastStateChangeTimestampMs = timestampMs;
+                }
+                mEnabled = true;
+            }
+        }
+
+        public void setState(int state, long timestampMs) {
+            if (mEnabled && mLastStateChangeTimestampMs >= 0 && mLastUpdateTimestampMs >= 0) {
+                if (timestampMs < mLastUpdateTimestampMs) {
+                    timestampMs = mLastUpdateTimestampMs;
+                }
+
+                if (timestampMs >= mLastStateChangeTimestampMs) {
+                    mStates[mCurrentState].mTimeInStateSinceUpdate +=
+                            timestampMs - mLastStateChangeTimestampMs;
+                } else {
+                    for (int i = 0; i < mStateCount; i++) {
+                        mStates[i].mTimeInStateSinceUpdate = 0;
+                    }
+                }
+            }
+            mCurrentState = state;
+            mLastStateChangeTimestampMs = timestampMs;
+        }
+
+        public long updateValue(long value, long timestampMs) {
+            long returnValue = 0;
+            if (mEnabled || mLastUpdateTimestampMs < mLastStateChangeTimestampMs) {
+                if (timestampMs < mLastStateChangeTimestampMs) {
+                    timestampMs = mLastStateChangeTimestampMs;
+                }
+
+                setState(mCurrentState, timestampMs);
+
+                if (mLastUpdateTimestampMs >= 0) {
+                    if (timestampMs > mLastUpdateTimestampMs) {
+                        long delta = value - mValue;
+                        if (delta >= 0) {
+                            returnValue = delta;
+                            long timeSinceUpdate = timestampMs - mLastUpdateTimestampMs;
+                            for (int i = 0; i < mStateCount; i++) {
+                                long timeInState = mStates[i].mTimeInStateSinceUpdate;
+                                if (timeInState > 0) {
+                                    mStates[i].mCounter += delta * timeInState / timeSinceUpdate;
+                                    mStates[i].mTimeInStateSinceUpdate = 0;
+                                }
+                            }
+                        } else {
+                            for (int i = 0; i < mStateCount; i++) {
+                                mStates[i].mTimeInStateSinceUpdate = 0;
+                            }
+                        }
+                    } else if (timestampMs < mLastUpdateTimestampMs) {
+                        for (int i = 0; i < mStateCount; i++) {
+                            mStates[i].mTimeInStateSinceUpdate = 0;
+                        }
+                    }
+                }
+            }
+            mValue = value;
+            mLastUpdateTimestampMs = timestampMs;
+            return returnValue;
+        }
+
+        public void incrementValue(long count, long timestampMs) {
+            updateValue(mValue + count, timestampMs);
+        }
+
+        public long getValue(int state) {
+            return mStates[state].mCounter;
+        }
+
+        public void reset() {
+            mLastStateChangeTimestampMs = -1;
+            mLastUpdateTimestampMs = -1;
+            for (int i = 0; i < mStateCount; i++) {
+                mStates[i].mTimeInStateSinceUpdate = 0;
+                mStates[i].mCounter = 0;
+            }
+        }
+
+        public void writeToParcel(Parcel parcel) {
+            parcel.writeInt(mStateCount);
+            for (int i = 0; i < mStateCount; i++) {
+                parcel.writeLong(mStates[i].mCounter);
+            }
+        }
+
+        public void initFromParcel(Parcel parcel) {
+            try {
+                for (int i = 0; i < mStateCount; i++) {
+                    mStates[i].mCounter = parcel.readLong();
+                }
+            } catch (Exception e) {
+                throw new BadParcelableException(e);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("[");
+            for (int state = 0; state < mStateCount; state++) {
+                if (state != 0) {
+                    sb.append(", ");
+                }
+                sb.append(state).append(": ").append(mStates[state].mCounter);
+            }
+            sb.append("]");
+            if (mLastUpdateTimestampMs >= 0) {
+                sb.append(" updated: ").append(mLastUpdateTimestampMs);
+            }
+            if (mLastStateChangeTimestampMs >= 0) {
+                sb.append(" currentState: ").append(mCurrentState);
+                if (mLastStateChangeTimestampMs > mLastUpdateTimestampMs) {
+                    sb.append(" stateChanged: ").append(mLastStateChangeTimestampMs);
+                }
+            } else {
+                sb.append(" currentState: none");
+            }
+            return sb.toString();
+        }
+    }
+
+    private static final HashMap<Long, LongMultiStateCounterRavenwood> sInstances =
+            new HashMap<>();
+    private static long sNextId = 1;
+
+    public static long native_init(int stateCount) {
+        LongMultiStateCounterRavenwood instance = new LongMultiStateCounterRavenwood(stateCount);
+        long instanceId = sNextId++;
+        sInstances.put(instanceId, instance);
+        return instanceId;
+    }
+
+    private static LongMultiStateCounterRavenwood getInstance(long instanceId) {
+        return sInstances.get(instanceId);
+    }
+
+    public static void native_setEnabled(long instanceId, boolean enabled,
+            long timestampMs) {
+        getInstance(instanceId).setEnabled(enabled, timestampMs);
+    }
+
+    public static int native_getStateCount(long instanceId) {
+        return getInstance(instanceId).mStateCount;
+    }
+
+    public static long native_updateValue(long instanceId, long value, long timestampMs) {
+        return getInstance(instanceId).updateValue(value, timestampMs);
+    }
+
+    public static void native_setState(long instanceId, int state, long timestampMs) {
+        getInstance(instanceId).setState(state, timestampMs);
+    }
+
+    public static void native_incrementValue(long instanceId, long count, long timestampMs) {
+        getInstance(instanceId).incrementValue(count, timestampMs);
+    }
+
+    public static long native_getCount(long instanceId, int state) {
+        return getInstance(instanceId).getValue(state);
+    }
+
+    public static void native_reset(long instanceId) {
+        getInstance(instanceId).reset();
+    }
+
+    public static void native_writeToParcel(long instanceId, Parcel parcel, int flags) {
+        getInstance(instanceId).writeToParcel(parcel);
+    }
+
+    public static long native_initFromParcel(Parcel parcel) {
+        int stateCount = parcel.readInt();
+        if (stateCount < 0 || stateCount > 0xEFFF) {
+            throw new BadParcelableException("stateCount out of range");
+        }
+        // LongMultiStateCounter.cpp uses AParcel, which throws on out-of-data.
+        if (parcel.dataPosition() >= parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        long instanceId = native_init(stateCount);
+        getInstance(instanceId).initFromParcel(parcel);
+        if (parcel.dataPosition() > parcel.dataSize()) {
+            throw new RuntimeException("Bad parcel");
+        }
+        return instanceId;
+    }
+
+    public static String native_toString(long instanceId) {
+        return getInstance(instanceId).toString();
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 97e09b8..06eeb47c 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -49,6 +49,7 @@
 class HostStubGen(val options: HostStubGenOptions) {
     fun run() {
         val errors = HostStubGenErrors()
+        val stats = HostStubGenStats()
 
         // Load all classes.
         val allClasses = loadClassStructures(options.inJar.get)
@@ -80,7 +81,14 @@
                 options.enableClassChecker.get,
                 allClasses,
                 errors,
+                stats,
         )
+
+        // Dump statistics, if specified.
+        options.statsFile.ifSet {
+            PrintWriter(it).use { pw -> stats.dump(pw) }
+            log.i("Dump file created at $it")
+        }
     }
 
     /**
@@ -237,6 +245,7 @@
             enableChecker: Boolean,
             classes: ClassNodes,
             errors: HostStubGenErrors,
+            stats: HostStubGenStats,
             ) {
         log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar)
         log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
@@ -254,7 +263,8 @@
                         while (inEntries.hasMoreElements()) {
                             val entry = inEntries.nextElement()
                             convertSingleEntry(inZip, entry, stubOutStream, implOutStream,
-                                    filter, packageRedirector, enableChecker, classes, errors)
+                                    filter, packageRedirector, enableChecker, classes, errors,
+                                    stats)
                         }
                         log.i("Converted all entries.")
                     }
@@ -287,6 +297,7 @@
             enableChecker: Boolean,
             classes: ClassNodes,
             errors: HostStubGenErrors,
+            stats: HostStubGenStats,
             ) {
         log.d("Entry: %s", entry.name)
         log.withIndent {
@@ -300,7 +311,7 @@
             // If it's a class, convert it.
             if (name.endsWith(".class")) {
                 processSingleClass(inZip, entry, stubOutStream, implOutStream, filter,
-                        packageRedirector, enableChecker, classes, errors)
+                        packageRedirector, enableChecker, classes, errors, stats)
                 return
             }
 
@@ -354,6 +365,7 @@
             enableChecker: Boolean,
             classes: ClassNodes,
             errors: HostStubGenErrors,
+            stats: HostStubGenStats,
             ) {
         val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "")
         val classPolicy = filter.getPolicyForClass(classInternalName)
@@ -370,7 +382,7 @@
                     stubOutStream.putNextEntry(newEntry)
                     convertClass(classInternalName, /*forImpl=*/false, bis,
                             stubOutStream, filter, packageRedirector, enableChecker, classes,
-                            errors)
+                            errors, stats)
                     stubOutStream.closeEntry()
                 }
             }
@@ -383,7 +395,7 @@
                     implOutStream.putNextEntry(newEntry)
                     convertClass(classInternalName, /*forImpl=*/true, bis,
                             implOutStream, filter, packageRedirector, enableChecker, classes,
-                            errors)
+                            errors, stats)
                     implOutStream.closeEntry()
                 }
             }
@@ -403,6 +415,7 @@
             enableChecker: Boolean,
             classes: ClassNodes,
             errors: HostStubGenErrors,
+            stats: HostStubGenStats,
             ) {
         val cr = ClassReader(input)
 
@@ -420,6 +433,7 @@
                 enablePostTrace = options.enablePostTrace.get,
                 enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection.get,
                 errors = errors,
+                stats = stats,
         )
         outVisitor = BaseAdapter.getVisitor(classInternalName, classes, outVisitor, filter,
                 packageRedirector, forImpl, visitorOptions)
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index d2ead18..9f5d524 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -108,6 +108,8 @@
         var enablePostTrace: SetOnce<Boolean> = SetOnce(false),
 
         var enableNonStubMethodCallDetection: SetOnce<Boolean> = SetOnce(false),
+
+        var statsFile: SetOnce<String?> = SetOnce(null),
 ) {
     companion object {
 
@@ -252,6 +254,8 @@
                         "--verbose-log" -> setLogFile(LogLevel.Verbose, nextArg())
                         "--debug-log" -> setLogFile(LogLevel.Debug, nextArg())
 
+                        "--stats-file" -> ret.statsFile.setNextStringArg()
+
                         else -> throw ArgumentsException("Unknown option: $arg")
                     }
                 } catch (e: SetOnce.SetMoreThanOnceException) {
@@ -387,6 +391,7 @@
               enablePreTrace=$enablePreTrace,
               enablePostTrace=$enablePostTrace,
               enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection,
+              statsFile=$statsFile,
             }
             """.trimIndent()
     }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
new file mode 100644
index 0000000..fe4072f
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen
+
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.filters.FilterPolicyWithReason
+import java.io.PrintWriter
+
+open class HostStubGenStats {
+    data class Stats(
+            var supported: Int = 0,
+            var total: Int = 0,
+            val children: MutableMap<String, Stats> = mutableMapOf<String, Stats>(),
+    )
+
+    private val stats = mutableMapOf<String, Stats>()
+
+    fun onVisitPolicyForMethod(fullClassName: String, policy: FilterPolicyWithReason) {
+        if (policy.isIgnoredForStats) return
+
+        val packageName = resolvePackageName(fullClassName)
+        val className = resolveClassName(fullClassName)
+
+        val packageStats = stats.getOrPut(packageName) { Stats() }
+        val classStats = packageStats.children.getOrPut(className) { Stats() }
+
+        if (policy.policy.isSupported) {
+            packageStats.supported += 1
+            classStats.supported += 1
+        }
+        packageStats.total += 1
+        classStats.total += 1
+    }
+
+    fun dump(pw: PrintWriter) {
+        pw.printf("PackageName,ClassName,SupportedMethods,TotalMethods\n")
+        stats.forEach { (packageName, packageStats) ->
+            if (packageStats.supported > 0) {
+                packageStats.children.forEach { (className, classStats) ->
+                    pw.printf("%s,%s,%d,%d\n", packageName, className,
+                            classStats.supported, classStats.total)
+                }
+            }
+        }
+    }
+
+    private fun resolvePackageName(fullClassName: String): String {
+        val start = fullClassName.lastIndexOf('/')
+        return fullClassName.substring(0, start).toHumanReadableClassName()
+    }
+
+    private fun resolveClassName(fullClassName: String): String {
+        val start = fullClassName.lastIndexOf('/')
+        val end = fullClassName.indexOf('$')
+        if (end == -1) {
+            return fullClassName.substring(start + 1)
+        } else {
+            return fullClassName.substring(start + 1, end)
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
index 9317996..4d21106 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
@@ -111,6 +111,16 @@
             }
         }
 
+    /** Returns whether a policy is considered supported. */
+    val isSupported: Boolean
+        get() {
+            return when (this) {
+                // TODO: handle native method with no substitution as being unsupported
+                Stub, StubClass, Keep, KeepClass, SubstituteAndStub, SubstituteAndKeep -> true
+                else -> false
+            }
+        }
+
     fun getSubstitutionBasePolicy(): FilterPolicy {
         return when (this) {
             SubstituteAndKeep -> Keep
@@ -136,4 +146,4 @@
     fun withReason(reason: String): FilterPolicyWithReason {
         return FilterPolicyWithReason(this, reason)
     }
-}
\ No newline at end of file
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
index b64a2f5..53eb5a8 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
@@ -63,4 +63,15 @@
     override fun toString(): String {
         return "[$policy - reason: $reason]"
     }
-}
\ No newline at end of file
+
+    /** Returns whether this policy should be ignored for stats. */
+    val isIgnoredForStats: Boolean
+        get() {
+            return reason.contains("anonymous-inner-class")
+                    || reason.contains("is-annotation")
+                    || reason.contains("is-enum")
+                    || reason.contains("is-synthetic-method")
+                    || reason.contains("special-class")
+                    || reason.contains("substitute-from")
+        }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index ea7d1d0..78b13fd 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -119,14 +119,14 @@
             if (cn.isEnum()) {
                 mn?.let { mn ->
                     if (isAutoGeneratedEnumMember(mn)) {
-                        return memberPolicy.withReason(classPolicy.reason).wrapReason("enum")
+                        return memberPolicy.withReason(classPolicy.reason).wrapReason("is-enum")
                     }
                 }
             }
 
             // Keep (or stub) all members of annotations.
             if (cn.isAnnotation()) {
-                return memberPolicy.withReason(classPolicy.reason).wrapReason("annotation")
+                return memberPolicy.withReason(classPolicy.reason).wrapReason("is-annotation")
             }
 
             mn?.let {
@@ -134,7 +134,7 @@
                     // For synthetic methods (such as lambdas), let's just inherit the class's
                     // policy.
                     return memberPolicy.withReason(classPolicy.reason).wrapReason(
-                            "synthetic method")
+                            "is-synthetic-method")
                 }
             }
         }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 7fdd944..6ad83fb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -132,23 +132,24 @@
                                             throw ParseException(
                                                     "Policy for AIDL classes already defined")
                                         }
-                                        aidlPolicy = policy.withReason("$FILTER_REASON (AIDL)")
+                                        aidlPolicy = policy.withReason(
+                                                "$FILTER_REASON (special-class AIDL)")
                                     }
                                     SpecialClass.FeatureFlags -> {
                                         if (featureFlagsPolicy != null) {
                                             throw ParseException(
                                                     "Policy for feature flags already defined")
                                         }
-                                        featureFlagsPolicy =
-                                                policy.withReason("$FILTER_REASON (feature flags)")
+                                        featureFlagsPolicy = policy.withReason(
+                                                "$FILTER_REASON (special-class feature flags)")
                                     }
                                     SpecialClass.Sysprops -> {
                                         if (syspropsPolicy != null) {
                                             throw ParseException(
                                                     "Policy for sysprops already defined")
                                         }
-                                        syspropsPolicy =
-                                                policy.withReason("$FILTER_REASON (sysprops)")
+                                        syspropsPolicy = policy.withReason(
+                                                "$FILTER_REASON (special-class sysprops)")
                                     }
                                 }
                             }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 21cfd4b..c20aa8b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -16,6 +16,7 @@
 package com.android.hoststubgen.visitors
 
 import com.android.hoststubgen.HostStubGenErrors
+import com.android.hoststubgen.HostStubGenStats
 import com.android.hoststubgen.LogLevel
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.UnifiedVisitor
@@ -50,6 +51,7 @@
      */
     data class Options (
             val errors: HostStubGenErrors,
+            val stats: HostStubGenStats,
             val enablePreTrace: Boolean,
             val enablePostTrace: Boolean,
             val enableNonStubMethodCallDetection: Boolean,
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index 416b782..beca945 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -141,6 +141,11 @@
             substituted: Boolean,
             superVisitor: MethodVisitor?,
     ): MethodVisitor? {
+        // Record statistics about visiting this method when visible.
+        if ((access and Opcodes.ACC_PRIVATE) == 0) {
+            options.stats.onVisitPolicyForMethod(currentClassName, policy)
+        }
+
         // Inject method log, if needed.
         var innerVisitor = superVisitor