Merge "Add some more owners for android/opengl." into main
diff --git a/core/api/current.txt b/core/api/current.txt
index acb4146..3fde9a6 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
@@ -24405,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);
@@ -24416,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();
@@ -24452,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();
@@ -24462,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);
@@ -55858,6 +55875,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);
@@ -55877,6 +55895,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 c10cc73..50c9c33 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -100,6 +100,7 @@
     field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
     field @FlaggedApi("com.android.internal.camera.flags.camera_hsum_permission") public static final String CAMERA_HEADLESS_SYSTEM_USER = "android.permission.CAMERA_HEADLESS_SYSTEM_USER";
     field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
+    field @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") public static final String CAMERA_PRIVACY_ALLOWLIST = "android.permission.CAMERA_PRIVACY_ALLOWLIST";
     field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
     field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD";
     field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT";
@@ -4656,11 +4657,15 @@
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int);
+    method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.Map<java.lang.String,java.lang.Boolean> getCameraPrivacyAllowlist();
+    method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public int getSensorPrivacyState(int, int);
+    method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isCameraPrivacyEnabled(@NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int, int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
     method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean);
+    method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int);
   }
 
   public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener {
@@ -4670,6 +4675,7 @@
 
   public static class SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams {
     method public int getSensor();
+    method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") public int getState();
     method public int getToggleType();
     method public boolean isEnabled();
   }
@@ -5676,6 +5682,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 +5718,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 +5802,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 +5812,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 +5995,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;
   }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index fc095d4..1e30a32 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1483,6 +1483,7 @@
 
   public final class SensorPrivacyManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
+    method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int, int);
   }
 
   public static class SensorPrivacyManager.Sources {
@@ -3894,7 +3895,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/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/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/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/hardware/CameraPrivacyAllowlistEntry.aidl b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
new file mode 100644
index 0000000..838e41e
--- /dev/null
+++ b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+/** @hide */
+parcelable CameraPrivacyAllowlistEntry {
+    String packageName;
+    boolean isMandatory;
+}
+
diff --git a/core/java/android/hardware/ISensorPrivacyListener.aidl b/core/java/android/hardware/ISensorPrivacyListener.aidl
index 2ac21d2..19ae302 100644
--- a/core/java/android/hardware/ISensorPrivacyListener.aidl
+++ b/core/java/android/hardware/ISensorPrivacyListener.aidl
@@ -25,5 +25,6 @@
     //   frameworks/native/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl
     // =============== Beginning of transactions used on native side as well ======================
     void onSensorPrivacyChanged(int toggleType, int sensor, boolean enabled);
+    void onSensorPrivacyStateChanged(int toggleType, int sensor, int state);
     // =============== End of transactions used on native side as well ============================
 }
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index 9cf329c..851ce2a 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware;
 
+import android.hardware.CameraPrivacyAllowlistEntry;
 import android.hardware.ISensorPrivacyListener;
 
 /** @hide */
@@ -45,6 +46,22 @@
     void setToggleSensorPrivacy(int userId, int source, int sensor, boolean enable);
 
     void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
+    List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist();
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
+    int getToggleSensorPrivacyState(int toggleType, int sensor);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)")
+    void setToggleSensorPrivacyState(int userId, int source, int sensor, int state);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)")
+    void setToggleSensorPrivacyStateForProfileGroup(int userId, int source, int sensor, int  state);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
+    boolean isCameraPrivacyEnabled(String packageName);
+
     // =============== End of transactions used on native side as well ============================
 
     void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token,
@@ -53,4 +70,4 @@
     boolean requiresAuthentication();
 
     void showSensorUseDialog(int sensor);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 18c95bfb..4c0a4b9 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -17,6 +17,7 @@
 package android.hardware;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -38,9 +39,11 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.camera.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -215,13 +218,41 @@
         public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED;
 
         /**
+         * Constant indicating privacy is enabled except for the automotive driver assistance apps
+         * which are helpful for driving.
+         */
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
+                SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS;
+
+         /**
+         * Constant indicating privacy is enabled except for the automotive driver assistance apps
+         * which are required by car manufacturer for driving.
+         */
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
+                SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS;
+
+        /**
+         * Constant indicating privacy is enabled except for the automotive driver assistance apps
+         * which are both helpful for driving and also apps required by car manufacturer for
+         * driving.
+         */
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
+                SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_APPS;
+
+        /**
          * Types of state which can exist for a sensor privacy toggle
          *
          * @hide
          */
         @IntDef(value = {
                 ENABLED,
-                DISABLED
+                DISABLED,
+                AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS,
+                AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS,
+                AUTOMOTIVE_DRIVER_ASSISTANCE_APPS
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface StateType {}
@@ -266,6 +297,19 @@
             private int mToggleType;
             private int mSensor;
             private boolean mEnabled;
+            private int mState;
+
+            @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+            private SensorPrivacyChangedParams(int toggleType, int sensor, int state) {
+                mToggleType = toggleType;
+                mSensor = sensor;
+                mState = state;
+                if (state == StateTypes.ENABLED) {
+                    mEnabled = true;
+                } else {
+                    mEnabled = false;
+                }
+            }
 
             private SensorPrivacyChangedParams(int toggleType, int sensor, boolean enabled) {
                 mToggleType = toggleType;
@@ -284,6 +328,12 @@
             public boolean isEnabled() {
                 return mEnabled;
             }
+
+            @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+            public @StateTypes.StateType int getState() {
+                return mState;
+            }
+
         }
     }
 
@@ -319,6 +369,9 @@
     private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>,
             OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>();
 
+    @GuardedBy("mLock")
+    private ArrayMap<String, Boolean> mCameraPrivacyAllowlist = null;
+
     /** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local
      * listeners */
     @NonNull
@@ -328,12 +381,33 @@
             synchronized (mLock) {
                 for (int i = 0; i < mToggleListeners.size(); i++) {
                     OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
-                    mToggleListeners.valueAt(i).execute(() -> listener
-                            .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener
-                                    .SensorPrivacyChangedParams(toggleType, sensor, enabled)));
+                    if (Flags.privacyAllowlist()) {
+                        int state = enabled ?  StateTypes.ENABLED : StateTypes.DISABLED;
+                        mToggleListeners.valueAt(i).execute(() -> listener
+                                .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener
+                                        .SensorPrivacyChangedParams(toggleType, sensor, state)));
+                    } else {
+                        mToggleListeners.valueAt(i).execute(() -> listener
+                                .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener
+                                        .SensorPrivacyChangedParams(toggleType, sensor, enabled)));
+                    }
                 }
             }
         }
+
+        @Override
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        public void onSensorPrivacyStateChanged(int toggleType, int sensor, int state) {
+            synchronized (mLock) {
+                for (int i = 0; i < mToggleListeners.size(); i++) {
+                    OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
+                    mToggleListeners.valueAt(i).execute(() -> listener
+                            .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener
+                                    .SensorPrivacyChangedParams(toggleType, sensor, state)));
+                }
+            }
+        }
+
     };
 
     /** Whether the singleton ISensorPrivacyListener has been registered */
@@ -649,6 +723,73 @@
     }
 
     /**
+     * Returns sensor privacy state for a specific sensor.
+     *
+     * @return int sensor privacy state.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    public @StateTypes.StateType int getSensorPrivacyState(@ToggleType int toggleType,
+            @Sensors.Sensor int sensor) {
+        try {
+            return mService.getToggleSensorPrivacyState(toggleType, sensor);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+  /**
+     * Returns if camera privacy is enabled for a specific package.
+     *
+     * @return boolean sensor privacy state.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    public boolean isCameraPrivacyEnabled(@NonNull String packageName) {
+        try {
+            return mService.isCameraPrivacyEnabled(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns camera privacy allowlist.
+     *
+     * @return List of automotive driver assistance packages for
+     * privacy allowlisting. The returned map includes the package
+     * name as key and the value is a Boolean which tells if that package
+     * is required by the car manufacturer as mandatory package for driving.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    public @NonNull Map<String, Boolean>  getCameraPrivacyAllowlist() {
+        synchronized (mLock) {
+            if (mCameraPrivacyAllowlist == null) {
+                mCameraPrivacyAllowlist = new ArrayMap<>();
+                try {
+                    for (CameraPrivacyAllowlistEntry entry :
+                            mService.getCameraPrivacyAllowlist()) {
+                        mCameraPrivacyAllowlist.put(entry.packageName, entry.isMandatory);
+                    }
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            return mCameraPrivacyAllowlist;
+        }
+    }
+
+    /**
      * Sets sensor privacy to the specified state for an individual sensor.
      *
      * @param sensor the sensor which to change the state for
@@ -677,6 +818,22 @@
      * Sets sensor privacy to the specified state for an individual sensor.
      *
      * @param sensor the sensor which to change the state for
+     * @param state the state to which sensor privacy should be set.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    public void setSensorPrivacyState(@Sensors.Sensor int sensor,
+            @StateTypes.StateType int state) {
+        setSensorPrivacyState(resolveSourceFromCurrentContext(), sensor, state);
+    }
+
+    /**
+     * Sets sensor privacy to the specified state for an individual sensor.
+     *
+     * @param sensor the sensor which to change the state for
      * @param enable the state to which sensor privacy should be set.
      *
      * @hide
@@ -708,6 +865,27 @@
     }
 
     /**
+     * Sets sensor privacy to the specified state for an individual sensor.
+     *
+     * @param sensor the sensor which to change the state for
+     * @param state the state to which sensor privacy should be set.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    public void setSensorPrivacyState(@Sources.Source int source, @Sensors.Sensor int sensor,
+            @StateTypes.StateType int state) {
+        try {
+            mService.setToggleSensorPrivacyState(mContext.getUserId(), source, sensor, state);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+    }
+
+    /**
      * Sets sensor privacy to the specified state for an individual sensor for the profile group of
      * context's user.
      *
@@ -745,6 +923,28 @@
     }
 
     /**
+     * Sets sensor privacy to the specified state for an individual sensor for the profile group of
+     * context's user.
+     *
+     * @param source the source using which the sensor is toggled.
+     * @param sensor the sensor which to change the state for
+     * @param state the state to which sensor privacy should be set.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    public void setSensorPrivacyStateForProfileGroup(@Sources.Source int source,
+            @Sensors.Sensor int sensor, @StateTypes.StateType int state) {
+        try {
+            mService.setToggleSensorPrivacyStateForProfileGroup(mContext.getUserId(), source,
+                    sensor, state);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Don't show dialogs to turn off sensor privacy for this package.
      *
      * @param suppress Whether to suppress or re-enable.
@@ -865,6 +1065,12 @@
                             boolean enabled) {
                         listener.onAllSensorPrivacyChanged(enabled);
                     }
+
+                    @Override
+                    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+                    public void onSensorPrivacyStateChanged(int toggleType, int sensor,
+                            int state) {
+                    }
                 };
                 mListeners.put(listener, iListener);
             }
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/provider/Settings.java b/core/java/android/provider/Settings.java
index be9915c..26f46cf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11830,6 +11830,12 @@
         public static final String MEDIA_CONTROLS_LOCK_SCREEN = "media_controls_lock_screen";
 
         /**
+         * Whether to enable camera extensions software fallback.
+         * @hide
+         */
+        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/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/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/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/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/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto
index 9359528..e368c6a 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -91,6 +91,9 @@
     enum StateType {
         ENABLED = 1;
         DISABLED = 2;
+        AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS = 3;
+        AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS = 4;
+        AUTO_DRIVER_ASSISTANCE_APPS = 5;
     }
 
     // DEPRECATED
@@ -134,4 +137,4 @@
     // Source for which sensor privacy was toggled.
     optional Source source = 1;
 
-}
\ No newline at end of file
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a425bb0..316001a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1730,6 +1730,16 @@
         android:description="@string/permdesc_cameraHeadlessSystemUser"
         android:protectionLevel="signature" />
 
+
+    <!-- @SystemApi Allows camera access of allowlisted driver assistance apps
+         to be controlled separately.
+         <p> Not for use by third-party applications.
+         @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist")
+         @hide
+    -->
+    <permission android:name="android.permission.CAMERA_PRIVACY_ALLOWLIST"
+        android:protectionLevel="signature|privileged" />
+
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device sensors                           -->
     <!-- ====================================================================== -->
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/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/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/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/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/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/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/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index 2f0fc51..98c0217 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -23,6 +23,7 @@
 import android.content.DialogInterface.BUTTON_POSITIVE
 import android.content.Intent
 import android.content.Intent.EXTRA_PACKAGE_NAME
+import android.content.pm.PackageManager
 import android.hardware.SensorPrivacyManager
 import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS
 import android.hardware.SensorPrivacyManager.EXTRA_SENSOR
@@ -31,6 +32,7 @@
 import android.os.Handler
 import android.window.OnBackInvokedDispatcher
 import androidx.annotation.OpenForTesting
+import com.android.internal.camera.flags.Flags
 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
@@ -90,14 +92,14 @@
             sensor = ALL_SENSORS
             val callback = IndividualSensorPrivacyController.Callback { _, _ ->
                 if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
-                        !sensorPrivacyController.isSensorBlocked(CAMERA)) {
+                        !isCameraBlocked(sensorUsePackageName)) {
                     finish()
                 }
             }
             sensorPrivacyListener = callback
             sensorPrivacyController.addCallback(callback)
             if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
-                    !sensorPrivacyController.isSensorBlocked(CAMERA)) {
+                    !isCameraBlocked(sensorUsePackageName)) {
                 finish()
                 return
             }
@@ -110,14 +112,22 @@
             }
             val callback = IndividualSensorPrivacyController.Callback {
                 whichSensor: Int, isBlocked: Boolean ->
-                if (whichSensor == sensor && !isBlocked) {
+                if (whichSensor != sensor) {
+                    // Ignore a callback; we're not interested in.
+                } else if ((whichSensor == CAMERA) && !isCameraBlocked(sensorUsePackageName)) {
+                    finish()
+                } else if ((whichSensor == MICROPHONE) && !isBlocked) {
                     finish()
                 }
             }
             sensorPrivacyListener = callback
             sensorPrivacyController.addCallback(callback)
 
-            if (!sensorPrivacyController.isSensorBlocked(sensor)) {
+            if ((sensor == CAMERA) && !isCameraBlocked(sensorUsePackageName)) {
+                finish()
+                return
+            } else if ((sensor == MICROPHONE) &&
+                    !sensorPrivacyController.isSensorBlocked(MICROPHONE)) {
                 finish()
                 return
             }
@@ -204,6 +214,22 @@
         recreate()
     }
 
+    private fun isAutomotive(): Boolean {
+        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    }
+
+    private fun isCameraBlocked(packageName: String): Boolean {
+        if (Flags.privacyAllowlist()) {
+            if (isAutomotive()) {
+                return sensorPrivacyController.isCameraPrivacyEnabled(packageName)
+            } else {
+                return sensorPrivacyController.isSensorBlocked(CAMERA)
+            }
+        } else {
+            return sensorPrivacyController.isSensorBlocked(CAMERA)
+        }
+    }
+
     private fun disableSensorPrivacy() {
         if (sensor == ALL_SENSORS) {
             sensorPrivacyController.setSensorBlocked(DIALOG, MICROPHONE, false)
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/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
index eb08f37..fb67358 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.annotation.FlaggedApi;
 import android.hardware.SensorPrivacyManager.Sensors.Sensor;
 import android.hardware.SensorPrivacyManager.Sources.Source;
 
+import com.android.internal.camera.flags.Flags;
+
 public interface IndividualSensorPrivacyController extends
         CallbackController<IndividualSensorPrivacyController.Callback> {
     void init();
@@ -42,6 +45,12 @@
      */
     boolean requiresAuthentication();
 
+    /**
+     * @return whether camera privacy is enabled for the package.
+     */
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    boolean isCameraPrivacyEnabled(String packageName);
+
     interface Callback {
         void onSensorBlockedChanged(@Sensor int sensor, boolean blocked);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index 87dfc99..8f768e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -19,6 +19,9 @@
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 
+import android.Manifest;
+import android.annotation.FlaggedApi;
+import android.annotation.RequiresPermission;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.SensorPrivacyManager.Sensors.Sensor;
 import android.hardware.SensorPrivacyManager.Sources.Source;
@@ -28,6 +31,8 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.util.Set;
 
 public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController {
@@ -102,6 +107,13 @@
     }
 
     @Override
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+    public boolean isCameraPrivacyEnabled(String packageName) {
+        return mSensorPrivacyManager.isCameraPrivacyEnabled(packageName);
+    }
+
+    @Override
     public void addCallback(@NonNull Callback listener) {
         mCallbacks.add(listener);
     }
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/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/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/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index a341b4a..2e14abb 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -263,6 +263,10 @@
     // location settings are off, for emergency purposes, as read from the configuration files.
     final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();
 
+    // These are the packages that are allow-listed to be able to access camera when
+    // the camera privacy state is for driver assistance apps only.
+    final ArrayMap<String, Boolean> mAllowlistCameraPrivacy = new ArrayMap<>();
+
     // These are the action strings of broadcasts which are whitelisted to
     // be delivered anonymously even to apps which target O+.
     final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
@@ -483,6 +487,10 @@
         return mAllowedAssociations;
     }
 
+    public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() {
+        return mAllowlistCameraPrivacy;
+    }
+
     public ArraySet<String> getBugreportWhitelistedPackages() {
         return mBugreportWhitelistedPackages;
     }
@@ -1062,6 +1070,22 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "camera-privacy-allowlisted-app" : {
+                        if (allowOverrideAppRestrictions) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            boolean isMandatory = XmlUtils.readBooleanAttribute(
+                                    parser, "mandatory", false);
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                mAllowlistCameraPrivacy.put(pkgname, isMandatory);
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "allow-ignore-location-settings": {
                         if (allowOverrideAppRestrictions) {
                             String pkgname = parser.getAttributeValue(null, "package");
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 5c95d43..63ea7b4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -106,6 +106,7 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -151,6 +152,7 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IAppOpsStartedCallback;
 import com.android.internal.app.MessageSamplingConfig;
+import com.android.internal.camera.flags.Flags;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.os.Clock;
 import com.android.internal.pm.pkg.component.ParsedAttribution;
@@ -223,6 +225,8 @@
      */
     private static final int CURRENT_VERSION = 1;
 
+    private SensorPrivacyManager mSensorPrivacyManager;
+
     // Write at most every 30 minutes.
     static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
 
@@ -1231,6 +1235,7 @@
                         }
                     }
                 });
+        mSensorPrivacyManager = SensorPrivacyManager.getInstance(mContext);
     }
 
     @VisibleForTesting
@@ -4642,6 +4647,10 @@
         return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
     }
 
+    private boolean isAutomotive() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
     private boolean isOpRestrictedLocked(int uid, int code, String packageName,
             String attributionTag, int virtualDeviceId, @Nullable RestrictionBypass appBypass,
             boolean isCheckOp) {
@@ -4658,6 +4667,14 @@
             }
         }
 
+        if (Flags.privacyAllowlist()) {
+            if ((code == OP_CAMERA) && isAutomotive()) {
+                if (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName)) {
+                    return true;
+                }
+            }
+        }
+
         int userHandle = UserHandle.getUserId(uid);
         restrictionSetCount = mOpUserRestrictions.size();
 
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/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 ffdc934..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;
@@ -1332,16 +1334,21 @@
         @Override
         public void onPackageDataCleared(String packageName, int uid) {
             synchronized (ImfLock.class) {
-                boolean changed = false;
+                // Note that one package may implement multiple IMEs.
+                final ArrayList<String> changedImes = new ArrayList<>();
                 for (InputMethodInfo imi : mSettings.getMethodList()) {
                     if (imi.getPackageName().equals(packageName)) {
-                        mAdditionalSubtypeMap.remove(imi.getId());
-                        changed = true;
+                        changedImes.add(imi.getId());
                     }
                 }
-                if (changed) {
+                final AdditionalSubtypeMap newMap =
+                        mAdditionalSubtypeMap.cloneWithRemoveOrSelf(changedImes);
+                if (newMap != mAdditionalSubtypeMap) {
+                    mAdditionalSubtypeMap = newMap;
                     AdditionalSubtypeUtils.save(
                             mAdditionalSubtypeMap, mSettings.getMethodMap(), mSettings.getUserId());
+                }
+                if (!changedImes.isEmpty()) {
                     mChangedPackages.add(packageName);
                 }
             }
@@ -1413,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());
                         }
@@ -1648,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);
@@ -1783,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) {
@@ -2016,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);
         }
@@ -4218,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 */);
@@ -4231,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());
+            }
         }
     }
 
@@ -5072,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
@@ -5112,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<>();
@@ -5512,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);
         }
@@ -5522,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);
     }
@@ -6572,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/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/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/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 59766ec..64cfc8d4 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -45,6 +45,11 @@
 import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
 import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
 import static android.hardware.SensorPrivacyManager.Sources.SHELL;
+import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
+import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
+import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
+import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED;
 import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
 import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
 import static android.os.UserHandle.USER_NULL;
@@ -52,6 +57,9 @@
 
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION;
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
@@ -63,8 +71,11 @@
 import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.write;
 
+import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -87,6 +98,7 @@
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.graphics.drawable.Icon;
+import android.hardware.CameraPrivacyAllowlistEntry;
 import android.hardware.ISensorPrivacyListener;
 import android.hardware.ISensorPrivacyManager;
 import android.hardware.SensorPrivacyManager;
@@ -123,6 +135,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.camera.flags.Flags;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
@@ -131,6 +144,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.pm.UserManagerInternal;
 
@@ -139,6 +153,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 
@@ -154,7 +169,24 @@
             SensorPrivacyService.class.getName() + ".action.disable_sensor_privacy";
 
     public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
-
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
+            PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
+            PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
+            PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    private static final int ACTION__TOGGLE_ON =
+            PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    private static final int ACTION__TOGGLE_OFF =
+            PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    private static final int ACTION__ACTION_UNKNOWN =
+            PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
     private final Context mContext;
     private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl;
     private final UserManagerInternal mUserManagerInternal;
@@ -176,6 +208,9 @@
     private CallStateHelper mCallStateHelper;
     private KeyguardManager mKeyguardManager;
 
+    List<CameraPrivacyAllowlistEntry> mCameraPrivacyAllowlist =
+            new ArrayList<CameraPrivacyAllowlistEntry>();
+
     private int mCurrentUser = USER_NULL;
 
     public SensorPrivacyService(Context context) {
@@ -192,6 +227,15 @@
         mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
         mNotificationManager = mContext.getSystemService(NotificationManager.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
+        ArrayMap<String, Boolean> cameraPrivacyAllowlist =
+                SystemConfig.getInstance().getCameraPrivacyAllowlist();
+
+        for (Map.Entry<String, Boolean> entry : cameraPrivacyAllowlist.entrySet()) {
+            CameraPrivacyAllowlistEntry ent = new CameraPrivacyAllowlistEntry();
+            ent.packageName = entry.getKey();
+            ent.isMandatory =  entry.getValue();
+            mCameraPrivacyAllowlist.add(ent);
+        }
     }
 
     @Override
@@ -324,8 +368,15 @@
                     mHandler, mHandler::handleSensorPrivacyChanged);
             mSensorPrivacyStateController.setSensorPrivacyListener(
                     mHandler,
-                    (toggleType, userId, sensor, state) -> mHandler.handleSensorPrivacyChanged(
-                            userId, toggleType, sensor, state.isEnabled()));
+                    (toggleType, userId, sensor, state) -> {
+                        mHandler.handleSensorPrivacyChanged(
+                                userId, toggleType, sensor, state.isEnabled());
+                        if (Flags.privacyAllowlist()) {
+                            mHandler.handleSensorPrivacyChanged(
+                                    userId, toggleType, sensor, state.getState());
+                        }
+                    });
+
         }
 
         // If sensor privacy is enabled for a sensor, but the device doesn't support sensor privacy
@@ -400,9 +451,15 @@
          * @param packageName The package name of the app using the sensor
          * @param sensor The sensor that is attempting to be used
          */
+        @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
         private void onSensorUseStarted(int uid, String packageName, int sensor) {
             UserHandle user = UserHandle.of(mCurrentUser);
-            if (!isCombinedToggleSensorPrivacyEnabled(sensor)) {
+
+            if (Flags.privacyAllowlist() && (sensor == CAMERA) && isAutomotive(mContext)) {
+                if (!isCameraPrivacyEnabled(packageName)) {
+                    return;
+                }
+            } else if (!isCombinedToggleSensorPrivacyEnabled(sensor)) {
                 return;
             }
 
@@ -727,6 +784,12 @@
                     == Configuration.UI_MODE_TYPE_TELEVISION;
         }
 
+        private boolean isAutomotive(Context context) {
+            int uiMode = context.getResources().getConfiguration().uiMode;
+            return (uiMode & Configuration.UI_MODE_TYPE_MASK)
+                    == Configuration.UI_MODE_TYPE_CAR;
+        }
+
         /**
          * Sets the sensor privacy to the provided state and notifies all listeners of the new
          * state.
@@ -766,6 +829,225 @@
             setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor, enable);
         }
 
+
+        @Override
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+        public void setToggleSensorPrivacyState(int userId, int source, int sensor, int state) {
+            if (DEBUG) {
+                Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+                        + " callingPid=" + Binder.getCallingPid()
+                        + " setToggleSensorPrivacyState("
+                        + "userId=" + userId
+                        + " source=" + source
+                        + " sensor=" + sensor
+                        + " state=" + state
+                        + ")");
+            }
+            enforceManageSensorPrivacyPermission();
+            if (userId == UserHandle.USER_CURRENT) {
+                userId = mCurrentUser;
+            }
+
+            if (!canChangeToggleSensorPrivacy(userId, sensor)) {
+                return;
+            }
+            if (!supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor)) {
+                // Do not enable sensor privacy if the device doesn't support it.
+                return;
+            }
+
+            setToggleSensorPrivacyStateUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor,
+                    state);
+        }
+
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        private void setToggleSensorPrivacyStateUnchecked(int toggleType, int userId, int source,
+                int sensor, int state) {
+            if (DEBUG) {
+                Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+                        + " callingPid=" + Binder.getCallingPid()
+                        + " setToggleSensorPrivacyStateUnchecked("
+                        + "userId=" + userId
+                        + " source=" + source
+                        + " sensor=" + sensor
+                        + " state=" + state
+                        + ")");
+            }
+            long[] lastChange = new long[1];
+            mSensorPrivacyStateController.atomic(() -> {
+                SensorState sensorState = mSensorPrivacyStateController
+                        .getState(toggleType, userId, sensor);
+                lastChange[0] = sensorState.getLastChange();
+                mSensorPrivacyStateController.setState(
+                        toggleType, userId, sensor, state, mHandler,
+                        changeSuccessful -> {
+                            if (changeSuccessful) {
+                                if (userId == mUserManagerInternal.getProfileParentId(userId)) {
+                                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                                            SensorPrivacyServiceImpl::logSensorPrivacyStateToggle,
+                                            this,
+                                            source, sensor, state, lastChange[0], false));
+                                }
+                            }
+                        });
+            });
+        }
+
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        private void logSensorPrivacyStateToggle(int source, int sensor, int state,
+                long lastChange, boolean onShutDown) {
+            long logMins = Math.max(0, (getCurrentTimeMillis() - lastChange) / (1000 * 60));
+
+            int logAction = ACTION__ACTION_UNKNOWN;
+            if (!onShutDown) {
+                switch(state) {
+                    case ENABLED :
+                        logAction = ACTION__TOGGLE_OFF;
+                        break;
+                    case DISABLED :
+                        logAction = ACTION__TOGGLE_ON;
+                        break;
+                    case AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS :
+                        logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
+                        break;
+                    case AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS :
+                        logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
+                        break;
+                    case AUTOMOTIVE_DRIVER_ASSISTANCE_APPS :
+                        logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+                        break;
+                    default :
+                        logAction = ACTION__ACTION_UNKNOWN;
+                        break;
+                }
+            }
+
+            int logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN;
+            switch(sensor) {
+                case CAMERA:
+                    logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
+                    break;
+                case MICROPHONE:
+                    logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__MICROPHONE;
+                    break;
+                default:
+                    logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN;
+                    break;
+            }
+
+            int logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN;
+            switch(source) {
+                case QS_TILE :
+                    logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__QS_TILE;
+                    break;
+                case DIALOG :
+                    logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__DIALOG;
+                    break;
+                case SETTINGS:
+                    logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SETTINGS;
+                    break;
+                default:
+                    logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN;
+                    break;
+            }
+
+            if (DEBUG || DEBUG_LOGGING) {
+                Log.d(TAG, "Logging sensor toggle interaction:" + " logSensor=" + logSensor
+                        + " logAction=" + logAction + " logSource=" + logSource + " logMins="
+                        + logMins);
+            }
+            write(PRIVACY_SENSOR_TOGGLE_INTERACTION, logSensor, logAction, logSource, logMins);
+
+        }
+
+        @Override
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+        public void setToggleSensorPrivacyStateForProfileGroup(int userId, int source, int sensor,
+                int  state) {
+            enforceManageSensorPrivacyPermission();
+            if (userId == UserHandle.USER_CURRENT) {
+                userId = mCurrentUser;
+            }
+            int parentId = mUserManagerInternal.getProfileParentId(userId);
+            forAllUsers(userId2 -> {
+                if (parentId == mUserManagerInternal.getProfileParentId(userId2)) {
+                    setToggleSensorPrivacyState(userId2, source, sensor, state);
+                }
+            });
+        }
+
+        @Override
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+        public List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist() {
+            enforceObserveSensorPrivacyPermission();
+            return mCameraPrivacyAllowlist;
+        }
+
+        @Override
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+        public boolean isCameraPrivacyEnabled(String packageName) {
+            if (DEBUG) {
+                Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+                        + " callingPid=" + Binder.getCallingPid()
+                        + " isCameraPrivacyEnabled("
+                        + "packageName=" + packageName
+                        + ")");
+            }
+            enforceObserveSensorPrivacyPermission();
+
+            int state =  mSensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, mCurrentUser,
+                    CAMERA).getState();
+            if (state == ENABLED) {
+                return true;
+            } else if (state == DISABLED) {
+                return false;
+            } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS) {
+                for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
+                    if ((packageName.equals(entry.packageName)) && !entry.isMandatory) {
+                        return false;
+                    }
+                }
+                return true;
+            } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS) {
+                for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
+                    if ((packageName.equals(entry.packageName)) && entry.isMandatory) {
+                        return false;
+                    }
+                }
+                return true;
+            } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_APPS) {
+                for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
+                    if (packageName.equals(entry.packageName)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+        public int getToggleSensorPrivacyState(int toggleType, int sensor) {
+            if (DEBUG) {
+                Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+                        + " callingPid=" + Binder.getCallingPid()
+                        + " getToggleSensorPrivacyState("
+                        + "toggleType=" + toggleType
+                        + " sensor=" + sensor
+                        + ")");
+            }
+            enforceObserveSensorPrivacyPermission();
+
+            return mSensorPrivacyStateController.getState(toggleType, mCurrentUser, sensor)
+                    .getState();
+        }
+
         private void setToggleSensorPrivacyUnchecked(int toggleType, int userId, int source,
                 int sensor, boolean enable) {
             if (DEBUG) {
@@ -899,16 +1181,23 @@
          * Enforces the caller contains the necessary permission to change the state of sensor
          * privacy.
          */
+        @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
         private void enforceManageSensorPrivacyPermission() {
-            enforcePermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY,
-                    "Changing sensor privacy requires the following permission: "
-                            + MANAGE_SENSOR_PRIVACY);
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) {
+                return;
+            }
+
+            String message = "Changing sensor privacy requires the following permission: "
+                    + MANAGE_SENSOR_PRIVACY;
+            throw new SecurityException(message);
         }
 
         /**
          * Enforces the caller contains the necessary permission to observe changes to the sate of
          * sensor privacy.
          */
+        @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
         private void enforceObserveSensorPrivacyPermission() {
             String systemUIPackage = mContext.getString(R.string.config_systemUi);
             int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal
@@ -917,15 +1206,13 @@
                 // b/221782106, possible race condition with role grant might bootloop device.
                 return;
             }
-            enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY,
-                    "Observing sensor privacy changes requires the following permission: "
-                            + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY);
-        }
-
-        private void enforcePermission(String permission, String message) {
-            if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) == PERMISSION_GRANTED) {
                 return;
             }
+
+            String message = "Observing sensor privacy changes requires the following permission: "
+                    + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY;
             throw new SecurityException(message);
         }
 
@@ -1293,11 +1580,13 @@
         }
 
         @Override
+        @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
         public void onShellCommand(FileDescriptor in, FileDescriptor out,
                 FileDescriptor err, String[] args, ShellCallback callback,
                 ResultReceiver resultReceiver) {
             (new ShellCommand() {
                 @Override
+                @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
                 public int onCommand(String cmd) {
                     if (cmd == null) {
                         return handleDefaultCommands(cmd);
@@ -1327,6 +1616,45 @@
                             setToggleSensorPrivacy(userId, SHELL, sensor, false);
                         }
                         break;
+                        case "automotive_driver_assistance_apps" : {
+                            if (Flags.privacyAllowlist()) {
+                                int sensor = sensorStrToId(getNextArgRequired());
+                                if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
+                                    pw.println("Command not valid for this sensor");
+                                    return -1;
+                                }
+
+                                setToggleSensorPrivacyState(userId, SHELL, sensor,
+                                        AUTOMOTIVE_DRIVER_ASSISTANCE_APPS);
+                            }
+                        }
+                        break;
+                        case "automotive_driver_assistance_helpful_apps" : {
+                            if (Flags.privacyAllowlist()) {
+                                int sensor = sensorStrToId(getNextArgRequired());
+                                if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
+                                    pw.println("Command not valid for this sensor");
+                                    return -1;
+                                }
+
+                                setToggleSensorPrivacyState(userId, SHELL, sensor,
+                                        AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS);
+                            }
+                        }
+                        break;
+                        case "automotive_driver_assistance_required_apps" : {
+                            if (Flags.privacyAllowlist()) {
+                                int sensor = sensorStrToId(getNextArgRequired());
+                                if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
+                                    pw.println("Command not valid for this sensor");
+                                    return -1;
+                                }
+
+                                setToggleSensorPrivacyState(userId, SHELL, sensor,
+                                        AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS);
+                            }
+                        }
+                        break;
                         default:
                             return handleDefaultCommands(cmd);
                     }
@@ -1349,6 +1677,24 @@
                     pw.println("  disable USER_ID SENSOR");
                     pw.println("    Disable privacy for a certain sensor.");
                     pw.println("");
+                    if (Flags.privacyAllowlist()) {
+                        if (isAutomotive(mContext)) {
+                            pw.println("  automotive_driver_assistance_apps USER_ID SENSOR");
+                            pw.println("    Disable privacy for automotive apps which help you"
+                                    + " drive and apps which are required by OEM");
+                            pw.println("");
+                            pw.println("  automotive_driver_assistance_helpful_apps "
+                                    + "USER_ID SENSOR");
+                            pw.println("    Disable privacy for automotive apps which "
+                                    + "help you drive.");
+                            pw.println("");
+                            pw.println("  automotive_driver_assistance_required_apps "
+                                    + "USER_ID SENSOR");
+                            pw.println("    Disable privacy for automotive apps which are "
+                                    + "required by OEM.");
+                            pw.println("");
+                        }
+                    }
                 }
             }).exec(this, in, out, err, args, callback, resultReceiver);
         }
@@ -1457,6 +1803,38 @@
             mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType);
         }
 
+        @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+        public void handleSensorPrivacyChanged(int userId, int toggleType, int sensor,
+                int state) {
+            if (userId == mCurrentUser) {
+                mSensorPrivacyServiceImpl.setGlobalRestriction(sensor,
+                        mSensorPrivacyServiceImpl.isCombinedToggleSensorPrivacyEnabled(sensor));
+            }
+
+            if (userId != mCurrentUser) {
+                return;
+            }
+            synchronized (mListenerLock) {
+                try {
+                    final int count = mToggleSensorListeners.beginBroadcast();
+                    for (int i = 0; i < count; i++) {
+                        ISensorPrivacyListener listener = mToggleSensorListeners.getBroadcastItem(
+                                i);
+                        try {
+                            listener.onSensorPrivacyStateChanged(toggleType, sensor, state);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Caught an exception notifying listener " + listener + ": ",
+                                    e);
+                        }
+                    }
+                } finally {
+                    mToggleSensorListeners.finishBroadcast();
+                }
+            }
+
+            mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType);
+        }
+
         public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key,
                 IBinder token) {
             sendMessage(PooledLambda.obtainMessage(
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java
index 9694958..0e29222 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java
@@ -16,9 +16,11 @@
 
 package com.android.server.sensorprivacy;
 
+import android.annotation.FlaggedApi;
 import android.os.Handler;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.camera.flags.Flags;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.internal.util.function.pooled.PooledLambda;
 
@@ -51,6 +53,14 @@
         }
     }
 
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    void setState(int toggleType, int userId, int sensor, int state, Handler callbackHandler,
+            SetStateResultCallback callback) {
+        synchronized (mLock) {
+            setStateLocked(toggleType, userId, sensor, state, callbackHandler, callback);
+        }
+    }
+
     void setSensorPrivacyListener(Handler handler,
             SensorPrivacyListener listener) {
         synchronized (mLock) {
@@ -128,6 +138,11 @@
             Handler callbackHandler, SetStateResultCallback callback);
 
     @GuardedBy("mLock")
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    abstract void setStateLocked(int toggleType, int userId, int sensor, int state,
+            Handler callbackHandler, SetStateResultCallback callback);
+
+    @GuardedBy("mLock")
     abstract void setSensorPrivacyListenerLocked(Handler handler,
             SensorPrivacyListener listener);
 
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java
index 3dcb4cf..2d96aeb 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java
@@ -16,8 +16,12 @@
 
 package com.android.server.sensorprivacy;
 
+import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
+
+import android.annotation.FlaggedApi;
 import android.os.Handler;
 
+import com.android.internal.camera.flags.Flags;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.internal.util.function.pooled.PooledLambda;
 
@@ -85,6 +89,33 @@
         sendSetStateCallback(callbackHandler, callback, false);
     }
 
+    @Override
+    @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+    void setStateLocked(int toggleType, int userId, int sensor, int state,
+            Handler callbackHandler, SetStateResultCallback callback) {
+        // Changing the SensorState's mEnabled updates the timestamp of its last change.
+        // A nonexistent state -> unmuted should not set the timestamp.
+        SensorState lastState = mPersistedState.getState(toggleType, userId, sensor);
+        if (lastState == null) {
+            if (state == DISABLED) {
+                sendSetStateCallback(callbackHandler, callback, false);
+                return;
+            } else {
+                SensorState sensorState = new SensorState(state);
+                mPersistedState.setState(toggleType, userId, sensor, sensorState);
+                notifyStateChangeLocked(toggleType, userId, sensor, sensorState);
+                sendSetStateCallback(callbackHandler, callback, true);
+                return;
+            }
+        }
+        if (lastState.setState(state)) {
+            notifyStateChangeLocked(toggleType, userId, sensor, lastState);
+            sendSetStateCallback(callbackHandler, callback, true);
+            return;
+        }
+        sendSetStateCallback(callbackHandler, callback, false);
+    }
+
     private void notifyStateChangeLocked(int toggleType, int userId, int sensor,
             SensorState sensorState) {
         if (mListenerHandler != null && mListener != null) {
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/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/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/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/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/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/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 068339b..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;
@@ -14312,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);
@@ -14401,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/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));