Merge "Fix AuthContainerViewTest failing with ConstraintBp Enabled" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 173cf6c..1478377 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6849,7 +6849,7 @@
   }
 
   public abstract static class Notification.Style {
-    ctor public Notification.Style();
+    ctor @Deprecated public Notification.Style();
     method public android.app.Notification build();
     method protected void checkBuilder();
     method protected android.widget.RemoteViews getStandardView(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4d3e252..78ac774 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2190,14 +2190,6 @@
 
 package android.app.ondeviceintelligence {
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Content implements android.os.Parcelable {
-    ctor public Content(@NonNull android.os.Bundle);
-    method public int describeContents();
-    method @NonNull public android.os.Bundle getData();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Content> CREATOR;
-  }
-
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback {
     method public void onDownloadCompleted(@NonNull android.os.PersistableBundle);
     method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle);
@@ -2233,11 +2225,11 @@
   }
 
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable {
-    ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int, @NonNull android.os.PersistableBundle);
-    ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
+    ctor public FeatureDetails(int, @NonNull android.os.PersistableBundle);
+    ctor public FeatureDetails(int);
     method public int describeContents();
     method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
-    method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
+    method public int getFeatureStatus();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
     field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2247,35 +2239,14 @@
     field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0
   }
 
-  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
-  }
-
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
-    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
-    field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
-    field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
-  }
-
-  public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException extends java.lang.Exception {
-    ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull String, @NonNull android.os.PersistableBundle);
-    ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull android.os.PersistableBundle);
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceException extends java.lang.Exception {
+    ctor public OnDeviceIntelligenceException(int, @NonNull String, @NonNull android.os.PersistableBundle);
+    ctor public OnDeviceIntelligenceException(int, @NonNull android.os.PersistableBundle);
+    ctor public OnDeviceIntelligenceException(int, @NonNull String);
+    ctor public OnDeviceIntelligenceException(int);
     method public int getErrorCode();
     method @NonNull public android.os.PersistableBundle getErrorParams();
-    field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000; // 0x3e8
-  }
-
-  public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException extends android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException {
-    ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull String, @NonNull android.os.PersistableBundle);
-    ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull android.os.PersistableBundle);
+    field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; // 0x64
     field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2
     field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3
     field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9
@@ -2291,10 +2262,28 @@
     field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf
     field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd
     field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; // 0xc8
   }
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
-    method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class OnDeviceIntelligenceManager {
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+    method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
+    field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
+    field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
+  }
+
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingCallback {
+    method public default void onDataAugmentRequest(@NonNull android.os.Bundle, @NonNull java.util.function.Consumer<android.os.Bundle>);
+    method public void onError(@NonNull android.app.ondeviceintelligence.OnDeviceIntelligenceException);
+    method public void onResult(@NonNull android.os.Bundle);
   }
 
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
@@ -2307,8 +2296,8 @@
     method public void onSignalReceived(@NonNull android.os.PersistableBundle);
   }
 
-  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
-    method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+  @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingProcessingCallback extends android.app.ondeviceintelligence.ProcessingCallback {
+    method public void onPartialResult(@NonNull android.os.Bundle);
   }
 
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
@@ -3338,7 +3327,7 @@
     method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @Nullable public static android.app.wearable.WearableSensingDataRequest getDataRequestFromIntent(@NonNull android.content.Intent);
     method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void startHotwordRecognition(@Nullable android.content.ComponentName, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void stopHotwordRecognition(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -6203,8 +6192,6 @@
     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;
   }
@@ -12956,38 +12943,26 @@
     ctor public OnDeviceIntelligenceService();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
-    method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+    method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
     method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
     method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
     method public abstract void onInferenceServiceConnected();
     method public abstract void onInferenceServiceDisconnected();
-    method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
-    method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+    method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
     field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
   }
 
-  public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
-    ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
-    ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
-    method public int getErrorCode();
-  }
-
-  public static class OnDeviceIntelligenceService.OnDeviceUpdateProcessingException extends android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException {
-    ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int);
-    ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int, @NonNull String);
-    field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
-  }
-
   @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
     ctor public OnDeviceSandboxedInferenceService();
     method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
     method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
-    method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
-    method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
-    method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+    method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+    method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+    method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+    method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
     method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
     method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
     field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1e72a06..62fc67b 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1885,8 +1885,6 @@
     Documentation mentions 'TODO'
 Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName):
     Documentation mentions 'TODO'
-Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback):
-    Documentation mentions 'TODO'
 Todo: android.hardware.camera2.params.StreamConfigurationMap:
     Documentation mentions 'TODO'
 Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int):
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2afc78c..a8352fa 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2951,7 +2951,9 @@
         new AppOpInfo.Builder(OP_ESTABLISH_VPN_MANAGER, OPSTR_ESTABLISH_VPN_MANAGER,
                 "ESTABLISH_VPN_MANAGER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
         new AppOpInfo.Builder(OP_ACCESS_RESTRICTED_SETTINGS, OPSTR_ACCESS_RESTRICTED_SETTINGS,
-                "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(AppOpsManager.MODE_ALLOWED)
+                "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(
+                        android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+                                ? MODE_DEFAULT : MODE_ALLOWED)
             .setDisableReset(true).setRestrictRead(true).build(),
         new AppOpInfo.Builder(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO,
                 "RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED)
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ed0c933..df566db 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -272,17 +272,10 @@
 
     @UnsupportedAppUsage
     private Context mOuterContext;
-
-    private final Object mThemeLock = new Object();
-
     @UnsupportedAppUsage
-    @GuardedBy("mThemeLock")
     private int mThemeResource = 0;
-
     @UnsupportedAppUsage
-    @GuardedBy("mThemeLock")
     private Resources.Theme mTheme = null;
-
     @UnsupportedAppUsage
     private PackageManager mPackageManager;
     private Context mReceiverRestrictedContext = null;
@@ -295,6 +288,7 @@
 
     private ContentCaptureOptions mContentCaptureOptions = null;
 
+    private final Object mSync = new Object();
     /**
      * Indicates this {@link Context} can not handle UI components properly and is not associated
      * with a {@link Display} instance.
@@ -346,18 +340,21 @@
      */
     private boolean mOwnsToken = false;
 
-    private final Object mDirsLock = new Object();
-    private volatile File mDatabasesDir;
-    @UnsupportedAppUsage private volatile File mPreferencesDir;
-    private volatile File mFilesDir;
-    private volatile File mCratesDir;
-    private volatile File mNoBackupFilesDir;
-    private volatile File[] mExternalFilesDirs;
-    private volatile File[] mObbDirs;
-    private volatile File mCacheDir;
-    private volatile File mCodeCacheDir;
-    private volatile File[] mExternalCacheDirs;
-    private volatile File[] mExternalMediaDirs;
+    @GuardedBy("mSync")
+    private File mDatabasesDir;
+    @GuardedBy("mSync")
+    @UnsupportedAppUsage
+    private File mPreferencesDir;
+    @GuardedBy("mSync")
+    private File mFilesDir;
+    @GuardedBy("mSync")
+    private File mCratesDir;
+    @GuardedBy("mSync")
+    private File mNoBackupFilesDir;
+    @GuardedBy("mSync")
+    private File mCacheDir;
+    @GuardedBy("mSync")
+    private File mCodeCacheDir;
 
     // The system service cache for the system services that are cached per-ContextImpl.
     @UnsupportedAppUsage
@@ -461,7 +458,7 @@
 
     @Override
     public void setTheme(int resId) {
-        synchronized (mThemeLock) {
+        synchronized (mSync) {
             if (mThemeResource != resId) {
                 mThemeResource = resId;
                 initializeTheme();
@@ -471,14 +468,14 @@
 
     @Override
     public int getThemeResId() {
-        synchronized (mThemeLock) {
+        synchronized (mSync) {
             return mThemeResource;
         }
     }
 
     @Override
     public Resources.Theme getTheme() {
-        synchronized (mThemeLock) {
+        synchronized (mSync) {
             if (mTheme != null) {
                 return mTheme;
             }
@@ -491,7 +488,6 @@
         }
     }
 
-    @GuardedBy("mThemeLock")
     private void initializeTheme() {
         if (mTheme == null) {
             mTheme = mResources.newTheme();
@@ -741,18 +737,12 @@
 
     @UnsupportedAppUsage
     private File getPreferencesDir() {
-        File localPreferencesDir = mPreferencesDir;
-        if (localPreferencesDir == null) {
-            synchronized (mDirsLock) {
-                localPreferencesDir = mPreferencesDir;
-                if (localPreferencesDir == null) {
-                    localPreferencesDir = new File(getDataDir(), "shared_prefs");
-                    ensurePrivateDirExists(localPreferencesDir);
-                    mPreferencesDir = localPreferencesDir;
-                }
+        synchronized (mSync) {
+            if (mPreferencesDir == null) {
+                mPreferencesDir = new File(getDataDir(), "shared_prefs");
             }
+            return ensurePrivateDirExists(mPreferencesDir);
         }
-        return localPreferencesDir;
     }
 
     @Override
@@ -794,16 +784,16 @@
     /**
      * Common-path handling of app data dir creation
      */
-    private static void ensurePrivateDirExists(File file) {
-        ensurePrivateDirExists(file, 0771, -1, null);
+    private static File ensurePrivateDirExists(File file) {
+        return ensurePrivateDirExists(file, 0771, -1, null);
     }
 
-    private static void ensurePrivateCacheDirExists(File file, String xattr) {
+    private static File ensurePrivateCacheDirExists(File file, String xattr) {
         final int gid = UserHandle.getCacheAppGid(Process.myUid());
-        ensurePrivateDirExists(file, 02771, gid, xattr);
+        return ensurePrivateDirExists(file, 02771, gid, xattr);
     }
 
-    private static void ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
+    private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
         if (!file.exists()) {
             final String path = file.getAbsolutePath();
             try {
@@ -831,22 +821,17 @@
                 }
             }
         }
+        return file;
     }
 
     @Override
     public File getFilesDir() {
-        File localFilesDir = mFilesDir;
-        if (localFilesDir == null) {
-            localFilesDir = mFilesDir;
-            synchronized (mDirsLock) {
-                if (localFilesDir == null) {
-                    localFilesDir = new File(getDataDir(), "files");
-                    ensurePrivateDirExists(localFilesDir);
-                    mFilesDir = localFilesDir;
-                }
+        synchronized (mSync) {
+            if (mFilesDir == null) {
+                mFilesDir = new File(getDataDir(), "files");
             }
+            return ensurePrivateDirExists(mFilesDir);
         }
-        return localFilesDir;
     }
 
     @Override
@@ -856,37 +841,25 @@
         final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
                 .toAbsolutePath().normalize();
 
-        File localCratesDir = mCratesDir;
-        if (localCratesDir == null) {
-            synchronized (mDirsLock) {
-                localCratesDir = mCratesDir;
-                if (localCratesDir == null) {
-                    localCratesDir = cratesRootPath.toFile();
-                    ensurePrivateDirExists(localCratesDir);
-                    mCratesDir = localCratesDir;
-                }
+        synchronized (mSync) {
+            if (mCratesDir == null) {
+                mCratesDir = cratesRootPath.toFile();
             }
+            ensurePrivateDirExists(mCratesDir);
         }
 
-        File crateDir = absoluteNormalizedCratePath.toFile();
-        ensurePrivateDirExists(crateDir);
-        return crateDir;
+        File cratedDir = absoluteNormalizedCratePath.toFile();
+        return ensurePrivateDirExists(cratedDir);
     }
 
     @Override
     public File getNoBackupFilesDir() {
-        File localNoBackupFilesDir = mNoBackupFilesDir;
-        if (localNoBackupFilesDir == null) {
-            synchronized (mDirsLock) {
-                localNoBackupFilesDir = mNoBackupFilesDir;
-                if (localNoBackupFilesDir == null) {
-                    localNoBackupFilesDir = new File(getDataDir(), "no_backup");
-                    ensurePrivateDirExists(localNoBackupFilesDir);
-                    mNoBackupFilesDir = localNoBackupFilesDir;
-                }
+        synchronized (mSync) {
+            if (mNoBackupFilesDir == null) {
+                mNoBackupFilesDir = new File(getDataDir(), "no_backup");
             }
+            return ensurePrivateDirExists(mNoBackupFilesDir);
         }
-        return localNoBackupFilesDir;
     }
 
     @Override
@@ -898,24 +871,13 @@
 
     @Override
     public File[] getExternalFilesDirs(String type) {
-        File[] localExternalFilesDirs = mExternalFilesDirs;
-        if (localExternalFilesDirs == null) {
-            synchronized (mDirsLock) {
-                localExternalFilesDirs = mExternalFilesDirs;
-                if (localExternalFilesDirs == null) {
-                    localExternalFilesDirs =
-                            Environment.buildExternalStorageAppFilesDirs(getPackageName());
-                    if (type != null) {
-                        localExternalFilesDirs =
-                                Environment.buildPaths(localExternalFilesDirs, type);
-                    }
-                    localExternalFilesDirs = ensureExternalDirsExistOrFilter(
-                            localExternalFilesDirs, true /* tryCreateInProcess */);
-                    mExternalFilesDirs = localExternalFilesDirs;
-                }
+        synchronized (mSync) {
+            File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
+            if (type != null) {
+                dirs = Environment.buildPaths(dirs, type);
             }
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
-        return localExternalFilesDirs;
     }
 
     @Override
@@ -927,51 +889,30 @@
 
     @Override
     public File[] getObbDirs() {
-        File[] localObbDirs = mObbDirs;
-        if (mObbDirs == null) {
-            synchronized (mDirsLock) {
-                localObbDirs = mObbDirs;
-                if (localObbDirs == null) {
-                    localObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
-                    localObbDirs = ensureExternalDirsExistOrFilter(
-                            localObbDirs, true /* tryCreateInProcess */);
-                    mObbDirs = localObbDirs;
-                }
-            }
+        synchronized (mSync) {
+            File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
-        return localObbDirs;
     }
 
     @Override
     public File getCacheDir() {
-        File localCacheDir = mCacheDir;
-        if (localCacheDir == null) {
-            synchronized (mDirsLock) {
-                localCacheDir = mCacheDir;
-                if (localCacheDir == null) {
-                    localCacheDir = new File(getDataDir(), "cache");
-                    ensurePrivateCacheDirExists(localCacheDir, XATTR_INODE_CACHE);
-                    mCacheDir = localCacheDir;
-                }
+        synchronized (mSync) {
+            if (mCacheDir == null) {
+                mCacheDir = new File(getDataDir(), "cache");
             }
+            return ensurePrivateCacheDirExists(mCacheDir, XATTR_INODE_CACHE);
         }
-        return localCacheDir;
     }
 
     @Override
     public File getCodeCacheDir() {
-        File localCodeCacheDir = mCodeCacheDir;
-        if (localCodeCacheDir == null) {
-            synchronized (mDirsLock) {
-                localCodeCacheDir = mCodeCacheDir;
-                if (localCodeCacheDir == null) {
-                    localCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
-                    ensurePrivateCacheDirExists(localCodeCacheDir, XATTR_INODE_CODE_CACHE);
-                    mCodeCacheDir = localCodeCacheDir;
-                }
+        synchronized (mSync) {
+            if (mCodeCacheDir == null) {
+                mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
             }
+            return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE);
         }
-        return localCodeCacheDir;
     }
 
     /**
@@ -992,37 +933,21 @@
 
     @Override
     public File[] getExternalCacheDirs() {
-        File[] localExternalCacheDirs = mExternalCacheDirs;
-        if (localExternalCacheDirs == null) {
-            synchronized (mDirsLock) {
-                localExternalCacheDirs = mExternalCacheDirs;
-                if (localExternalCacheDirs == null) {
-                    localExternalCacheDirs =
-                            Environment.buildExternalStorageAppCacheDirs(getPackageName());
-                    localExternalCacheDirs = ensureExternalDirsExistOrFilter(
-                            localExternalCacheDirs, false /* tryCreateInProcess */);
-                    mExternalCacheDirs = localExternalCacheDirs;
-                }
-            }
+        synchronized (mSync) {
+            File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
+            // We don't try to create cache directories in-process, because they need special
+            // setup for accurate quota tracking. This ensures the cache dirs are always
+            // created through StorageManagerService.
+            return ensureExternalDirsExistOrFilter(dirs, false /* tryCreateInProcess */);
         }
-        return localExternalCacheDirs;
     }
 
     @Override
     public File[] getExternalMediaDirs() {
-        File[] localExternalMediaDirs = mExternalMediaDirs;
-        if (localExternalMediaDirs == null) {
-            synchronized (mDirsLock) {
-                localExternalMediaDirs = mExternalMediaDirs;
-                if (localExternalMediaDirs == null) {
-                    localExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
-                    localExternalMediaDirs = ensureExternalDirsExistOrFilter(
-                            localExternalMediaDirs, true /* tryCreateInProcess */);
-                    mExternalMediaDirs = localExternalMediaDirs;
-                }
-            }
+        synchronized (mSync) {
+            File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+            return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
         }
-        return localExternalMediaDirs;
     }
 
     /**
@@ -1121,22 +1046,16 @@
     }
 
     private File getDatabasesDir() {
-        File localDatabasesDir = mDatabasesDir;
-        if (localDatabasesDir == null) {
-            synchronized (mDirsLock) {
-                localDatabasesDir = mDatabasesDir;
-                if (localDatabasesDir == null) {
-                    if ("android".equals(getPackageName())) {
-                        localDatabasesDir = new File("/data/system");
-                    } else {
-                        localDatabasesDir = new File(getDataDir(), "databases");
-                    }
-                    ensurePrivateDirExists(localDatabasesDir);
-                    mDatabasesDir = localDatabasesDir;
+        synchronized (mSync) {
+            if (mDatabasesDir == null) {
+                if ("android".equals(getPackageName())) {
+                    mDatabasesDir = new File("/data/system");
+                } else {
+                    mDatabasesDir = new File(getDataDir(), "databases");
                 }
             }
+            return ensurePrivateDirExists(mDatabasesDir);
         }
-        return localDatabasesDir;
     }
 
     @Override
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index cd4c0bc..7337a7c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3586,12 +3586,15 @@
      * Sets the token used for background operations for the pending intents associated with this
      * notification.
      *
+     * This token is automatically set during deserialization for you, you usually won't need to
+     * call this unless you want to change the existing token, if any.
+     *
      * @hide
      */
-    public void overrideAllowlistToken(IBinder token) {
-        mAllowlistToken = token;
+    public void clearAllowlistToken() {
+        mAllowlistToken = null;
         if (publicVersion != null) {
-            publicVersion.overrideAllowlistToken(token);
+            publicVersion.clearAllowlistToken();
         }
     }
 
@@ -7379,6 +7382,15 @@
     public static abstract class Style {
 
         /**
+         * @deprecated public access to the constructor of Style() is only useful for creating
+         * custom subclasses, but that has actually been impossible due to hidden abstract
+         * methods, so this constructor is now officially deprecated to clarify that this is
+         * intended to be disallowed.
+         */
+        @Deprecated
+        public Style() {}
+
+        /**
          * The number of items allowed simulatanously in the remote input history.
          * @hide
          */
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d01626e..fa4a400 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,8 @@
 import android.app.contentsuggestions.IContentSuggestionsManager;
 import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
 import android.app.job.JobSchedulerFrameworkInitializer;
+import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
 import android.app.people.PeopleManager;
 import android.app.prediction.AppPredictionManager;
 import android.app.role.RoleFrameworkInitializer;
@@ -1589,6 +1591,19 @@
                         return new WearableSensingManager(ctx.getOuterContext(), manager);
                     }});
 
+        registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class,
+                new CachedServiceFetcher<OnDeviceIntelligenceManager>() {
+                    @Override
+                    public OnDeviceIntelligenceManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder iBinder = ServiceManager.getServiceOrThrow(
+                                Context.ON_DEVICE_INTELLIGENCE_SERVICE);
+                        IOnDeviceIntelligenceManager manager =
+                                IOnDeviceIntelligenceManager.Stub.asInterface(iBinder);
+                        return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager);
+                    }
+                });
+
         registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
                 new CachedServiceFetcher<GrammaticalInflectionManager>() {
                     @Override
diff --git a/core/java/android/app/ondeviceintelligence/Content.java b/core/java/android/app/ondeviceintelligence/Content.java
deleted file mode 100644
index 51bd156..0000000
--- a/core/java/android/app/ondeviceintelligence/Content.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * Represents content sent to and received from the on-device inference service.
- * Can contain a collection of text, image, and binary parts or any combination of these.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public final class Content implements Parcelable {
-    //TODO: Improve javadoc after adding validation logic.
-    private static final String TAG = "Content";
-    private final Bundle mData;
-
-    /**
-     * Create a content object using a Bundle of only known types that are read-only.
-     */
-    public Content(@NonNull Bundle data) {
-        Objects.requireNonNull(data);
-        validateBundleData(data);
-        this.mData = data;
-    }
-
-    /**
-     * Returns the Content's data represented as a Bundle.
-     */
-    @NonNull
-    public Bundle getData() {
-        return mData;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeBundle(mData);
-    }
-
-    @Override
-    public int describeContents() {
-        int mask = 0;
-        mask |= mData.describeContents();
-        return mask;
-    }
-
-    @NonNull
-    public static final Creator<Content> CREATOR = new Creator<>() {
-        @Override
-        @NonNull
-        public Content createFromParcel(@NonNull Parcel in) {
-            return new Content(in.readBundle(getClass().getClassLoader()));
-        }
-
-        @Override
-        @NonNull
-        public Content[] newArray(int size) {
-            return new Content[size];
-        }
-    };
-
-    private void validateBundleData(Bundle unused) {
-        // TODO: Validate there are only known types.
-    }
-}
diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
index 684c71f..30c6e19 100644
--- a/core/java/android/app/ondeviceintelligence/DownloadCallback.java
+++ b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -105,7 +105,7 @@
     }
 
     /**
-     * Called when model download via MDD completed. The remote implementation can populate any
+     * Called when model download is completed. The remote implementation can populate any
      * associated download params like file stats etc. in this callback to inform the client.
      *
      * @param downloadParams params containing info about the completed download.
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
index 4a38c92..fd0379a 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -34,7 +34,6 @@
 @SystemApi
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
 public final class Feature implements Parcelable {
-    // TODO(b/325315604) - Check if we can expose non-hidden IntDefs in Framework.
     private final int mId;
     @Nullable
     private final String mName;
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
index f3cbd26..44930f2 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -60,6 +60,9 @@
     /** Underlying service is unavailable and feature status cannot be fetched. */
     public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4;
 
+    /**
+     * @hide
+     */
     @IntDef(value = {
             FEATURE_STATUS_UNAVAILABLE,
             FEATURE_STATUS_DOWNLOADABLE,
diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
index aba563f..8fc269e 100644
--- a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
@@ -16,15 +16,14 @@
 
 package android.app.ondeviceintelligence;
 
-import android.app.ondeviceintelligence.IProcessingSignal;
 import android.os.PersistableBundle;
 
 /**
- * Interface for Download callback to passed onto service implementation,
+ * Interface for Download callback to be passed onto service implementation,
  *
  * @hide
  */
-oneway interface IDownloadCallback {
+interface IDownloadCallback {
   void onDownloadStarted(long bytesToDownload) = 1;
   void onDownloadProgress(long bytesDownloaded) = 2;
   void onDownloadFailed(int failureStatus, String errorMessage, in PersistableBundle errorParams) = 3;
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index 360a809..0dbe181 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -21,7 +21,7 @@
  import android.os.ParcelFileDescriptor;
  import android.os.PersistableBundle;
  import android.os.RemoteCallback;
- import android.app.ondeviceintelligence.Content;
+ import android.os.Bundle;
  import android.app.ondeviceintelligence.Feature;
  import android.app.ondeviceintelligence.FeatureDetails;
  import android.app.ondeviceintelligence.IDownloadCallback;
@@ -39,7 +39,7 @@
   *
   * @hide
   */
- oneway interface IOnDeviceIntelligenceManager {
+ interface IOnDeviceIntelligenceManager {
       @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
       void getVersion(in RemoteCallback remoteCallback) = 1;
 
@@ -53,18 +53,20 @@
       void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback) = 4;
 
       @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
-      void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
+      void requestFeatureDownload(in Feature feature, in ICancellationSignal signal, in IDownloadCallback callback) = 5;
 
       @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
-      void requestTokenInfo(in Feature feature, in Content request, in  ICancellationSignal signal,
+      void requestTokenInfo(in Feature feature, in Bundle requestBundle, in  ICancellationSignal signal,
                                                         in ITokenInfoCallback tokenInfocallback) = 6;
 
       @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
-      void processRequest(in Feature feature, in Content request, int requestType, in  ICancellationSignal cancellationSignal, in IProcessingSignal signal,
-                                                        in IResponseCallback responseCallback) = 7;
+      void processRequest(in Feature feature, in Bundle requestBundle, int requestType, in  ICancellationSignal cancellationSignal,
+                                                in IProcessingSignal signal, in IResponseCallback responseCallback) = 7;
 
       @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
       void processRequestStreaming(in Feature feature,
-                    in Content request, int requestType, in  ICancellationSignal cancellationSignal, in  IProcessingSignal signal,
+                    in Bundle requestBundle, int requestType, in  ICancellationSignal cancellationSignal, in  IProcessingSignal signal,
                     in IStreamingResponseCallback streamingCallback) = 8;
+
+      String getRemoteServicePackageName() = 9;
  }
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 0adf305..45963d2 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -1,8 +1,7 @@
 package android.app.ondeviceintelligence;
 
-import android.app.ondeviceintelligence.Content;
-import android.app.ondeviceintelligence.IProcessingSignal;
 import android.os.PersistableBundle;
+import android.os.Bundle;
 import android.os.RemoteCallback;
 
 /**
@@ -11,7 +10,7 @@
   * @hide
   */
 interface IResponseCallback {
-    void onSuccess(in Content result) = 1;
+    void onSuccess(in Bundle resultBundle) = 1;
     void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
-    void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3;
+    void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 3;
 }
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index 132e53e..671abe3 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -1,10 +1,8 @@
 package android.app.ondeviceintelligence;
 
-import android.app.ondeviceintelligence.Content;
-import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.IProcessingSignal;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
+import android.os.Bundle;
 
 
 /**
@@ -13,8 +11,8 @@
   * @hide
   */
 interface IStreamingResponseCallback {
-    void onNewContent(in Content result) = 1;
-    void onSuccess(in Content result) = 2;
+    void onNewContent(in Bundle processedResult) = 1;
+    void onSuccess(in Bundle result) = 2;
     void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
-    void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 4;
+    void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 4;
 }
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
new file mode 100644
index 0000000..03ff563a
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.PersistableBundle;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Exception type to be used for errors related to on-device intelligence system service with
+ * appropriate error code.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public class OnDeviceIntelligenceException extends Exception {
+
+    public static final int PROCESSING_ERROR_UNKNOWN = 1;
+
+    /** Request passed contains bad data for e.g. format. */
+    public static final int PROCESSING_ERROR_BAD_DATA = 2;
+
+    /** Bad request for inputs. */
+    public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
+
+    /** Whole request was classified as not safe, and no response will be generated. */
+    public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
+
+    /** Underlying processing encountered an error and failed to compute results. */
+    public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
+
+    /** Encountered an error while performing IPC */
+    public static final int PROCESSING_ERROR_IPC_ERROR = 6;
+
+    /** Request was cancelled either by user signal or by the underlying implementation. */
+    public static final int PROCESSING_ERROR_CANCELLED = 7;
+
+    /** Underlying processing in the remote implementation is not available. */
+    public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
+
+    /** The service is currently busy. Callers should retry with exponential backoff. */
+    public static final int PROCESSING_ERROR_BUSY = 9;
+
+    /** Something went wrong with safety classification service. */
+    public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
+
+    /** Response generated was classified unsafe. */
+    public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
+
+    /** Request is too large to be processed. */
+    public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
+
+    /** Inference suspended so that higher-priority inference can run. */
+    public static final int PROCESSING_ERROR_SUSPENDED = 13;
+
+    /**
+     * Underlying processing encountered an internal error, like a violated precondition
+     * .
+     */
+    public static final int PROCESSING_ERROR_INTERNAL = 14;
+
+    /**
+     * The processing was not able to be passed on to the remote implementation, as the
+     * service
+     * was unavailable.
+     */
+    public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
+    /**
+     * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
+     */
+    public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100;
+
+    /**
+     * The connection to remote service failed and the processing state could not be updated.
+     */
+    public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200;
+
+
+    /**
+     * Error code associated with the on-device intelligence failure.
+     *
+     * @hide
+     */
+    @IntDef(
+            value = {
+                    PROCESSING_ERROR_UNKNOWN,
+                    PROCESSING_ERROR_BAD_DATA,
+                    PROCESSING_ERROR_BAD_REQUEST,
+                    PROCESSING_ERROR_REQUEST_NOT_SAFE,
+                    PROCESSING_ERROR_COMPUTE_ERROR,
+                    PROCESSING_ERROR_IPC_ERROR,
+                    PROCESSING_ERROR_CANCELLED,
+                    PROCESSING_ERROR_NOT_AVAILABLE,
+                    PROCESSING_ERROR_BUSY,
+                    PROCESSING_ERROR_SAFETY_ERROR,
+                    PROCESSING_ERROR_RESPONSE_NOT_SAFE,
+                    PROCESSING_ERROR_REQUEST_TOO_LARGE,
+                    PROCESSING_ERROR_SUSPENDED,
+                    PROCESSING_ERROR_INTERNAL,
+                    PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+                    ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+                    PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
+            }, open = true)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    @interface OnDeviceIntelligenceError {
+    }
+
+    private final int mErrorCode;
+    private final PersistableBundle mErrorParams;
+
+    /** Returns the error code of the exception. */
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /** Returns the error params of the exception. */
+    @NonNull
+    public PersistableBundle getErrorParams() {
+        return mErrorParams;
+    }
+
+    /**
+     * Creates a new OnDeviceIntelligenceException with the specified error code, error message and
+     * error params.
+     *
+     * @param errorCode The error code.
+     * @param errorMessage The error message.
+     * @param errorParams The error params.
+     */
+    public OnDeviceIntelligenceException(
+            @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage,
+            @NonNull PersistableBundle errorParams) {
+        super(errorMessage);
+        this.mErrorCode = errorCode;
+        this.mErrorParams = errorParams;
+    }
+
+    /**
+     * Creates a new OnDeviceIntelligenceException with the specified error code and error params.
+     *
+     * @param errorCode The error code.
+     * @param errorParams The error params.
+     */
+    public OnDeviceIntelligenceException(
+            @OnDeviceIntelligenceError int errorCode,
+            @NonNull PersistableBundle errorParams) {
+        this.mErrorCode = errorCode;
+        this.mErrorParams = errorParams;
+    }
+
+    /**
+     * Creates a new OnDeviceIntelligenceException with the specified error code and error message.
+     *
+     * @param errorCode The error code.
+     * @param errorMessage The error message.
+     */
+    public OnDeviceIntelligenceException(
+            @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage) {
+        super(errorMessage);
+        this.mErrorCode = errorCode;
+        this.mErrorParams = new PersistableBundle();
+    }
+
+    /**
+     * Creates a new OnDeviceIntelligenceException with the specified error code.
+     *
+     * @param errorCode The error code.
+     */
+    public OnDeviceIntelligenceException(
+            @OnDeviceIntelligenceError int errorCode) {
+        this.mErrorCode = errorCode;
+        this.mErrorParams = new PersistableBundle();
+    }
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index d195c4d..a465e3c 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -28,6 +28,7 @@
 import android.annotation.SystemService;
 import android.content.ComponentName;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -36,6 +37,7 @@
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.system.OsConstants;
 
 import androidx.annotation.IntDef;
 
@@ -63,7 +65,7 @@
 @SystemApi
 @SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public class OnDeviceIntelligenceManager {
+public final class OnDeviceIntelligenceManager {
     /**
      * @hide
      */
@@ -118,14 +120,13 @@
     @Nullable
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
     public String getRemoteServicePackageName() {
-        String serviceConfigValue = mContext.getResources().getString(
-                R.string.config_defaultOnDeviceSandboxedInferenceService);
-        ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue);
-        if (componentName != null) {
-            return componentName.getPackageName();
+        String result;
+        try{
+           result = mService.getRemoteServicePackageName();
+        } catch (RemoteException e){
+            throw e.rethrowFromSystemServer();
         }
-
-        return null;
+        return result;
     }
 
     /**
@@ -139,7 +140,7 @@
     public void getFeature(
             int featureId,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceManagerException> featureReceiver) {
+            @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceException> featureReceiver) {
         try {
             IFeatureCallback callback =
                     new IFeatureCallback.Stub() {
@@ -154,7 +155,7 @@
                                 PersistableBundle errorParams) {
                             Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
                                     () -> featureReceiver.onError(
-                                            new OnDeviceIntelligenceManagerException(
+                                            new OnDeviceIntelligenceException(
                                                     errorCode, errorMessage, errorParams))));
                         }
                     };
@@ -173,7 +174,7 @@
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
     public void listFeatures(
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceManagerException> featureListReceiver) {
+            @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceException> featureListReceiver) {
         try {
             IListFeaturesCallback callback =
                     new IListFeaturesCallback.Stub() {
@@ -188,7 +189,7 @@
                                 PersistableBundle errorParams) {
                             Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
                                     () -> featureListReceiver.onError(
-                                            new OnDeviceIntelligenceManagerException(
+                                            new OnDeviceIntelligenceException(
                                                     errorCode, errorMessage, errorParams))));
                         }
                     };
@@ -211,7 +212,7 @@
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
     public void getFeatureDetails(@NonNull Feature feature,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceManagerException> featureDetailsReceiver) {
+            @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> featureDetailsReceiver) {
         try {
             IFeatureDetailsCallback callback = new IFeatureDetailsCallback.Stub() {
 
@@ -226,7 +227,7 @@
                         PersistableBundle errorParams) {
                     Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> featureDetailsReceiver.onError(
-                                    new OnDeviceIntelligenceManagerException(errorCode,
+                                    new OnDeviceIntelligenceException(errorCode,
                                             errorMessage, errorParams))));
                 }
             };
@@ -243,9 +244,8 @@
      *
      * Note: If a feature was already requested for downloaded previously, the onDownloadFailed
      * callback would be invoked with {@link DownloadCallback#DOWNLOAD_FAILURE_STATUS_DOWNLOADING}.
-     * In such cases, clients should query the feature status via {@link #getFeatureStatus} to
-     * check
-     * on the feature's download status.
+     * In such cases, clients should query the feature status via {@link #getFeatureDetails} to
+     * check on the feature's download status.
      *
      * @param feature            feature to request download for.
      * @param callback           callback to populate updates about download status.
@@ -284,7 +284,7 @@
                 @Override
                 public void onDownloadCompleted(PersistableBundle downloadParams) {
                     Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
-                            () -> onDownloadCompleted(downloadParams)));
+                            () -> callback.onDownloadCompleted(downloadParams)));
                 }
             };
 
@@ -305,7 +305,8 @@
      * provided {@link Feature}.
      *
      * @param feature            feature associated with the request.
-     * @param request            request that contains the content data and associated params.
+     * @param request            request and associated params represented by the Bundle
+     *                           data.
      * @param outcomeReceiver    callback to populate the token info or exception in case of
      *                           failure.
      * @param cancellationSignal signal to invoke cancellation on the operation in the remote
@@ -313,11 +314,11 @@
      * @param callbackExecutor   executor to run the callback on.
      */
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
-    public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request,
+    public void requestTokenInfo(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor callbackExecutor,
             @NonNull OutcomeReceiver<TokenInfo,
-                    OnDeviceIntelligenceManagerException> outcomeReceiver) {
+                    OnDeviceIntelligenceException> outcomeReceiver) {
         try {
             ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
                 @Override
@@ -331,7 +332,7 @@
                         PersistableBundle errorParams) {
                     Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
                             () -> outcomeReceiver.onError(
-                                    new OnDeviceIntelligenceManagerProcessingException(
+                                    new OnDeviceIntelligenceException(
                                             errorCode, errorMessage, errorParams))));
                 }
             };
@@ -357,30 +358,30 @@
      * failure.
      *
      * @param feature            feature associated with the request.
-     * @param request            request that contains the Content data and
-     *                           associated params.
+     * @param request            request and associated params represented by the Bundle
+     *                           data.
      * @param requestType        type of request being sent for processing the content.
      * @param cancellationSignal signal to invoke cancellation.
      * @param processingSignal   signal to send custom signals in the
      *                           remote implementation.
      * @param callbackExecutor   executor to run the callback on.
-     * @param responseCallback   callback to populate the response content and
+     * @param processingCallback callback to populate the response content and
      *                           associated params.
      */
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
 
-    public void processRequest(@NonNull Feature feature, @Nullable Content request,
+    public void processRequest(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
             @RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull ProcessingOutcomeReceiver responseCallback) {
+            @NonNull ProcessingCallback processingCallback) {
         try {
             IResponseCallback callback = new IResponseCallback.Stub() {
                 @Override
-                public void onSuccess(Content result) {
+                public void onSuccess(@InferenceParams Bundle result) {
                     Binder.withCleanCallingIdentity(() -> {
-                        callbackExecutor.execute(() -> responseCallback.onResult(result));
+                        callbackExecutor.execute(() -> processingCallback.onResult(result));
                     });
                 }
 
@@ -388,16 +389,16 @@
                 public void onFailure(int errorCode, String errorMessage,
                         PersistableBundle errorParams) {
                     Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
-                            () -> responseCallback.onError(
-                                    new OnDeviceIntelligenceManagerProcessingException(
+                            () -> processingCallback.onError(
+                                    new OnDeviceIntelligenceException(
                                             errorCode, errorMessage, errorParams))));
                 }
 
                 @Override
-                public void onDataAugmentRequest(@NonNull Content content,
+                public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request,
                         @NonNull RemoteCallback contentCallback) {
                     Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
-                            () -> responseCallback.onDataAugmentRequest(content, result -> {
+                            () -> processingCallback.onDataAugmentRequest(request, result -> {
                                 Bundle bundle = new Bundle();
                                 bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
                                 callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
@@ -430,15 +431,15 @@
      * Variation of {@link #processRequest} that asynchronously processes a request in a
      * streaming
      * fashion, where new content is pushed to caller in chunks via the
-     * {@link StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete,
-     * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally
-     * populate the complete the full response {@link Content} as part of the callback in cases
-     * when the final response contains an enhanced aggregation of the Contents already
+     * {@link StreamingProcessingCallback#onPartialResult}. After the streaming is complete,
+     * the service should call {@link StreamingProcessingCallback#onResult} and can optionally
+     * populate the complete the full response {@link Bundle} as part of the callback in cases
+     * when the final response contains an enhanced aggregation of the contents already
      * streamed.
      *
      * @param feature                   feature associated with the request.
-     * @param request                   request that contains the Content data and associated
-     *                                  params.
+     * @param request                   request and associated params represented by the Bundle
+     *                                  data.
      * @param requestType               type of request being sent for processing the content.
      * @param cancellationSignal        signal to invoke cancellation.
      * @param processingSignal          signal to send custom signals in the
@@ -448,27 +449,27 @@
      * @param callbackExecutor          executor to run the callback on.
      */
     @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
-    public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request,
+    public void processRequestStreaming(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
             @RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) {
+            @NonNull StreamingProcessingCallback streamingProcessingCallback) {
         try {
             IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
                 @Override
-                public void onNewContent(Content result) {
+                public void onNewContent(@InferenceParams Bundle result) {
                     Binder.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(
-                                () -> streamingResponseCallback.onNewContent(result));
+                                () -> streamingProcessingCallback.onPartialResult(result));
                     });
                 }
 
                 @Override
-                public void onSuccess(Content result) {
+                public void onSuccess(@InferenceParams Bundle result) {
                     Binder.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(
-                                () -> streamingResponseCallback.onResult(result));
+                                () -> streamingProcessingCallback.onResult(result));
                     });
                 }
 
@@ -477,18 +478,18 @@
                         PersistableBundle errorParams) {
                     Binder.withCleanCallingIdentity(() -> {
                         callbackExecutor.execute(
-                                () -> streamingResponseCallback.onError(
-                                        new OnDeviceIntelligenceManagerProcessingException(
+                                () -> streamingProcessingCallback.onError(
+                                        new OnDeviceIntelligenceException(
                                                 errorCode, errorMessage, errorParams)));
                     });
                 }
 
 
                 @Override
-                public void onDataAugmentRequest(@NonNull Content content,
+                public void onDataAugmentRequest(@NonNull @InferenceParams Bundle content,
                         @NonNull RemoteCallback contentCallback) {
                     Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
-                            () -> streamingResponseCallback.onDataAugmentRequest(content,
+                            () -> streamingProcessingCallback.onDataAugmentRequest(content,
                                     contentResponse -> {
                                         Bundle bundle = new Bundle();
                                         bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
@@ -519,7 +520,7 @@
     }
 
 
-    /** Request inference with provided Content and Params. */
+    /** Request inference with provided Bundle and Params. */
     public static final int REQUEST_TYPE_INFERENCE = 0;
 
     /**
@@ -530,7 +531,7 @@
      */
     public static final int REQUEST_TYPE_PREPARE = 1;
 
-    /** Request Embeddings of the passed-in Content. */
+    /** Request Embeddings of the passed-in Bundle. */
     public static final int REQUEST_TYPE_EMBEDDINGS = 2;
 
     /**
@@ -547,154 +548,30 @@
     public @interface RequestType {
     }
 
-
     /**
-     * Exception type to be populated in callbacks to the methods under
-     * {@link OnDeviceIntelligenceManager}.
+     * {@link Bundle}s annotated with this type will be validated that they are in-effect read-only
+     * when passed to inference service via Binder IPC. Following restrictions apply :
+     * <ul>
+     * <li> Any primitive types or their collections can be added as usual.</li>
+     * <li>IBinder objects should *not* be added.</li>
+     * <li>Parcelable data which has no active-objects, should be added as
+     * {@link Bundle#putByteArray}</li>
+     * <li>Parcelables have active-objects, only following types will be allowed</li>
+     * <ul>
+     *  <li>{@link Bitmap} set as {@link Bitmap#setImmutable()}</li>
+     *  <li>{@link android.database.CursorWindow}</li>
+     *  <li>{@link android.os.ParcelFileDescriptor} opened in
+     *  {@link android.os.ParcelFileDescriptor#MODE_READ_ONLY}</li>
+     *  <li>{@link android.os.SharedMemory} set to {@link OsConstants#PROT_READ}</li>
+     * </ul>
+     * </ul>
+     *
+     * In all other scenarios the system-server might throw a
+     * {@link android.os.BadParcelableException} if the Bundle validation fails.
+     *
+     * @hide
      */
-    public static class OnDeviceIntelligenceManagerException extends Exception {
-        /**
-         * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
-         */
-        public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
-
-        /**
-         * Error code to be used for on device intelligence manager failures.
-         *
-         * @hide
-         */
-        @IntDef(
-                value = {
-                        ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE
-                }, open = true)
-        @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
-        @interface OnDeviceIntelligenceManagerErrorCode {
-        }
-
-        private final int mErrorCode;
-        private final PersistableBundle errorParams;
-
-        public OnDeviceIntelligenceManagerException(
-                @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage,
-                @NonNull PersistableBundle errorParams) {
-            super(errorMessage);
-            this.mErrorCode = errorCode;
-            this.errorParams = errorParams;
-        }
-
-        public OnDeviceIntelligenceManagerException(
-                @OnDeviceIntelligenceManagerErrorCode int errorCode,
-                @NonNull PersistableBundle errorParams) {
-            this.mErrorCode = errorCode;
-            this.errorParams = errorParams;
-        }
-
-        public int getErrorCode() {
-            return mErrorCode;
-        }
-
-        @NonNull
-        public PersistableBundle getErrorParams() {
-            return errorParams;
-        }
-    }
-
-    /**
-     * Exception type to be populated in callbacks to the methods under
-     * {@link OnDeviceIntelligenceManager#processRequest} or
-     * {@link OnDeviceIntelligenceManager#processRequestStreaming} .
-     */
-    public static class OnDeviceIntelligenceManagerProcessingException extends
-            OnDeviceIntelligenceManagerException {
-
-        public static final int PROCESSING_ERROR_UNKNOWN = 1;
-
-        /** Request passed contains bad data for e.g. format. */
-        public static final int PROCESSING_ERROR_BAD_DATA = 2;
-
-        /** Bad request for inputs. */
-        public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
-
-        /** Whole request was classified as not safe, and no response will be generated. */
-        public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
-
-        /** Underlying processing encountered an error and failed to compute results. */
-        public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
-
-        /** Encountered an error while performing IPC */
-        public static final int PROCESSING_ERROR_IPC_ERROR = 6;
-
-        /** Request was cancelled either by user signal or by the underlying implementation. */
-        public static final int PROCESSING_ERROR_CANCELLED = 7;
-
-        /** Underlying processing in the remote implementation is not available. */
-        public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
-
-        /** The service is currently busy. Callers should retry with exponential backoff. */
-        public static final int PROCESSING_ERROR_BUSY = 9;
-
-        /** Something went wrong with safety classification service. */
-        public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
-
-        /** Response generated was classified unsafe. */
-        public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
-
-        /** Request is too large to be processed. */
-        public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
-
-        /** Inference suspended so that higher-priority inference can run. */
-        public static final int PROCESSING_ERROR_SUSPENDED = 13;
-
-        /**
-         * Underlying processing encountered an internal error, like a violated precondition
-         * .
-         */
-        public static final int PROCESSING_ERROR_INTERNAL = 14;
-
-        /**
-         * The processing was not able to be passed on to the remote implementation, as the
-         * service
-         * was unavailable.
-         */
-        public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
-
-        /**
-         * Error code of failed processing request.
-         *
-         * @hide
-         */
-        @IntDef(
-                value = {
-                        PROCESSING_ERROR_UNKNOWN,
-                        PROCESSING_ERROR_BAD_DATA,
-                        PROCESSING_ERROR_BAD_REQUEST,
-                        PROCESSING_ERROR_REQUEST_NOT_SAFE,
-                        PROCESSING_ERROR_COMPUTE_ERROR,
-                        PROCESSING_ERROR_IPC_ERROR,
-                        PROCESSING_ERROR_CANCELLED,
-                        PROCESSING_ERROR_NOT_AVAILABLE,
-                        PROCESSING_ERROR_BUSY,
-                        PROCESSING_ERROR_SAFETY_ERROR,
-                        PROCESSING_ERROR_RESPONSE_NOT_SAFE,
-                        PROCESSING_ERROR_REQUEST_TOO_LARGE,
-                        PROCESSING_ERROR_SUSPENDED,
-                        PROCESSING_ERROR_INTERNAL,
-                        PROCESSING_ERROR_SERVICE_UNAVAILABLE
-                }, open = true)
-        @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
-        @interface ProcessingError {
-        }
-
-        public OnDeviceIntelligenceManagerProcessingException(
-                @ProcessingError int errorCode, @NonNull String errorMessage,
-                @NonNull PersistableBundle errorParams) {
-            super(errorCode, errorMessage, errorParams);
-        }
-
-        public OnDeviceIntelligenceManagerProcessingException(
-                @ProcessingError int errorCode,
-                @NonNull PersistableBundle errorParams) {
-            super(errorCode, errorParams);
-        }
+    @Target({ElementType.PARAMETER, ElementType.FIELD})
+    public @interface InferenceParams {
     }
 }
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
new file mode 100644
index 0000000..4d936ea
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
+
+import java.util.function.Consumer;
+
+/**
+ * Callback to populate the processed response or any error that occurred during the
+ * request processing. This callback also provides a method to request additional data to be
+ * augmented to the request-processing, using the partial response that was already
+ * processed in the remote implementation.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface ProcessingCallback {
+    /**
+     * Invoked when request has been processed and result is ready to be propagated to the
+     * caller.
+     *
+     * @param result Response to be passed as a result.
+     */
+    void onResult(@NonNull @InferenceParams Bundle result);
+
+    /**
+     * Called when the request processing fails. The failure details are indicated by the
+     * {@link OnDeviceIntelligenceException} passed as an argument to this method.
+     *
+     * @param error An exception with more details about the error that occurred.
+     */
+    void onError(@NonNull OnDeviceIntelligenceException error);
+
+    /**
+     * Callback to be invoked in cases where the remote service needs to perform retrieval or
+     * transformation operations based on a partially processed request, in order to augment the
+     * final response, by using the additional context sent via this callback.
+     *
+     * @param processedContent The content payload that should be used to augment ongoing request.
+     * @param contentConsumer  The augmentation data that should be sent to remote
+     *                         service for further processing a request. Bundle passed in here is
+     *                         expected to be non-null or EMPTY when there is no response.
+     */
+    default void onDataAugmentRequest(
+            @NonNull @InferenceParams Bundle processedContent,
+            @NonNull Consumer<Bundle> contentConsumer) {
+        contentConsumer.accept(Bundle.EMPTY);
+    }
+}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
deleted file mode 100644
index b0b6e19..0000000
--- a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.OutcomeReceiver;
-
-import java.util.function.Consumer;
-
-/**
- * Response Callback to populate the processed response or any error that occurred during the
- * request processing. This callback also provides a method to request additional data to be
- * augmented to the request-processing, using  the partial {@link Content} that was already
- * processed in the remote implementation.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface ProcessingOutcomeReceiver extends
-        OutcomeReceiver<Content,
-                OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
-    /**
-     * Callback to be invoked in cases where the remote service needs to perform retrieval or
-     * transformation operations based on a partially processed request, in order to augment the
-     * final response, by using the additional context sent via this callback.
-     *
-     * @param content         The content payload that should be used to augment ongoing request.
-     * @param contentConsumer The augmentation data that should be sent to remote
-     *                        service for further processing a request.
-     */
-    default void onDataAugmentRequest(@NonNull Content content,
-            @NonNull Consumer<Content> contentConsumer) {
-        contentConsumer.accept(null);
-    }
-}
diff --git a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
similarity index 65%
rename from core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
rename to core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
index ac2b032..41f1807 100644
--- a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
@@ -21,19 +21,21 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
 
 /**
- * Streaming variant of outcome receiver to populate response while processing a given request,
- * possibly in chunks to provide a async processing behaviour to the caller.
+ * Streaming variant of {@link ProcessingCallback} to populate response while processing a given
+ * request, possibly in chunks to provide a async processing behaviour to the caller.
  *
  * @hide
  */
 @SystemApi
 @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver {
+public interface StreamingProcessingCallback extends ProcessingCallback {
     /**
-     * Callback that would be invoked when a part of the response i.e. some {@link Content} is
-     * already processed and needs to be passed onto the caller.
+     * Callback that would be invoked when a part of the response i.e. some response is
+     * already processed, and needs to be passed onto the caller.
      */
-    void onNewContent(@NonNull Content content);
+    void onPartialResult(@NonNull @InferenceParams Bundle partialResult);
 }
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index 9573e6f..df6d2a6 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -246,7 +246,10 @@
      * @param executor Executor on which to run the consumer callback
      * @param statusConsumer A consumer that handles the status codes, which is returned
      *                 right after the call.
+     * @deprecated Use {@link #provideConnection(ParcelFileDescriptor, Executor, Consumer)} instead
+     *     to provide a remote wearable device connection to the WearableSensingService
      */
+    @Deprecated
     @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
     public void provideDataStream(
             @NonNull ParcelFileDescriptor parcelFileDescriptor,
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 09e59d3..47edba6 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -72,14 +72,20 @@
 
 flag {
     namespace: "credential_manager"
-    name: "clear_credentials_api_fix_enabled"
+    name: "clear_credentials_fix_enabled"
     description: "Fixes bug in clearCredential API that causes indefinite suspension"
     bug: "314926460"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
 
 flag {
     namespace: "credential_manager"
-    name: "hybrid_filter_fix_enabled"
+    name: "hybrid_filter_opt_fix_enabled"
     description: "Removes capability check from hybrid implementation"
     bug: "323923403"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 0208fed..be9f0a0 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -295,7 +295,7 @@
          * called.
          *
          * @param view The customized view information.
-         * @return This builder.re
+         * @return This builder.
          */
         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
         @NonNull
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 85f9900..6962811 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -227,14 +227,18 @@
     private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
                                                      Integer format,
                                                      StreamConfigurationMap streamMap) {
-        // Per API contract it is assumed that the extension is able to support all
-        // camera advertised sizes for a given format in case it doesn't return
-        // a valid non-empty size list.
         ArrayList<Size> ret = getSupportedSizes(sizesList, format);
-        Size[] supportedSizes = streamMap.getOutputSizes(format);
-        if ((ret.isEmpty()) && (supportedSizes != null)) {
-            ret.addAll(Arrays.asList(supportedSizes));
+
+        if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888) {
+            // Per API contract it is assumed that the extension is able to support all
+            // camera advertised sizes for JPEG and YUV_420_888 in case it doesn't return
+            // a valid non-empty size list.
+            Size[] supportedSizes = streamMap.getOutputSizes(format);
+            if ((ret.isEmpty()) && (supportedSizes != null)) {
+                ret.addAll(Arrays.asList(supportedSizes));
+            }
         }
+
         return ret;
     }
 
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 48aa1bd..905caf0 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -164,16 +164,18 @@
 
     /**
      * Sets the isReliable field of the message
+     *
+     * @hide
      */
-    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
     public void setIsReliable(boolean isReliable) {
         mIsReliable = isReliable;
     }
 
     /**
      * Sets the message sequence number of the message
+     *
+     * @hide
      */
-    @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
     public void setMessageSequenceNumber(int messageSequenceNumber) {
         mMessageSequenceNumber = messageSequenceNumber;
     }
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 73257ed..799c7545 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -20,7 +20,6 @@
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.ITokenInfoCallback;
 import android.app.ondeviceintelligence.IProcessingSignal;
-import android.app.ondeviceintelligence.Content;
 import android.app.ondeviceintelligence.Feature;
 import android.os.ICancellationSignal;
 import android.os.PersistableBundle;
@@ -35,12 +34,12 @@
  */
 oneway interface IOnDeviceSandboxedInferenceService {
     void registerRemoteStorageService(in IRemoteStorageService storageService);
-    void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+    void requestTokenInfo(int callerUid, in Feature feature, in Bundle request, in ICancellationSignal cancellationSignal,
                             in ITokenInfoCallback tokenInfoCallback);
-    void processRequest(int callerUid, in Feature feature, in Content request, in int requestType,
+    void processRequest(int callerUid, in Feature feature, in Bundle request, in int requestType,
                         in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
                         in IResponseCallback callback);
-    void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType,
+    void processRequestStreaming(int callerUid, in Feature feature, in Bundle request, in int requestType,
                                 in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
                                 in IStreamingResponseCallback callback);
     void updateProcessingState(in Bundle processingState,
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index fce3689..27e8628 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -32,7 +32,9 @@
 import android.app.ondeviceintelligence.IFeatureCallback;
 import android.app.ondeviceintelligence.IFeatureDetailsCallback;
 import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
 import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Bundle;
@@ -48,14 +50,8 @@
 
 import com.android.internal.infra.AndroidFuture;
 
-import androidx.annotation.IntDef;
-
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -232,9 +228,9 @@
      * @param callbackExecutor executor to the run status callback on.
      * @param statusReceiver   receiver to get status of the update state operation.
      */
-    public final void updateProcessingState(@NonNull Bundle processingState,
+    public final void updateProcessingState(@NonNull @InferenceParams Bundle processingState,
             @NonNull @CallbackExecutor Executor callbackExecutor,
-            @NonNull OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> statusReceiver) {
+            @NonNull OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> statusReceiver) {
         Objects.requireNonNull(callbackExecutor);
         if (mRemoteProcessingService == null) {
             throw new IllegalStateException("Remote processing service is unavailable.");
@@ -254,7 +250,7 @@
                         public void onFailure(int errorCode, String errorMessage) {
                             Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
                                     () -> statusReceiver.onError(
-                                            new OnDeviceUpdateProcessingException(
+                                            new OnDeviceIntelligenceException(
                                                     errorCode, errorMessage))));
                         }
                     });
@@ -265,7 +261,7 @@
     }
 
     private OutcomeReceiver<Feature,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback(
+            OnDeviceIntelligenceException> wrapFeatureCallback(
             IFeatureCallback featureCallback) {
         return new OutcomeReceiver<>() {
             @Override
@@ -279,7 +275,7 @@
 
             @Override
             public void onError(
-                    @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+                    @NonNull OnDeviceIntelligenceException exception) {
                 try {
                     featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
                             exception.getErrorParams());
@@ -291,7 +287,7 @@
     }
 
     private OutcomeReceiver<List<Feature>,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapListFeaturesCallback(
+            OnDeviceIntelligenceException> wrapListFeaturesCallback(
             IListFeaturesCallback listFeaturesCallback) {
         return new OutcomeReceiver<>() {
             @Override
@@ -305,7 +301,7 @@
 
             @Override
             public void onError(
-                    @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+                    @NonNull OnDeviceIntelligenceException exception) {
                 try {
                     listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
                             exception.getErrorParams());
@@ -317,7 +313,7 @@
     }
 
     private OutcomeReceiver<FeatureDetails,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureDetailsCallback(
+            OnDeviceIntelligenceException> wrapFeatureDetailsCallback(
             IFeatureDetailsCallback featureStatusCallback) {
         return new OutcomeReceiver<>() {
             @Override
@@ -331,7 +327,7 @@
 
             @Override
             public void onError(
-                    @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+                    @NonNull OnDeviceIntelligenceException exception) {
                 try {
                     featureStatusCallback.onFailure(exception.getErrorCode(),
                             exception.getMessage(), exception.getErrorParams());
@@ -444,7 +440,7 @@
      */
     public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
             @NonNull OutcomeReceiver<FeatureDetails,
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
+                    OnDeviceIntelligenceException> featureDetailsCallback);
 
 
     /**
@@ -455,7 +451,7 @@
      */
     public abstract void onGetFeature(int callerUid, int featureId,
             @NonNull OutcomeReceiver<Feature,
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
+                    OnDeviceIntelligenceException> featureCallback);
 
     /**
      * List all features which are available in the remote implementation. The implementation might
@@ -465,7 +461,7 @@
      * @param listFeaturesCallback callback to populate the features list.
      */
     public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
+            OnDeviceIntelligenceException> listFeaturesCallback);
 
     /**
      * Provides a long value representing the version of the remote implementation processing
@@ -474,60 +470,4 @@
      * @param versionConsumer consumer to populate the version.
      */
     public abstract void onGetVersion(@NonNull LongConsumer versionConsumer);
-
-
-    /**
-     * Exception type to be populated when calls to {@link #updateProcessingState} fail.
-     */
-    public static class OnDeviceUpdateProcessingException extends
-            OnDeviceIntelligenceServiceException {
-        /**
-         * The connection to remote service failed and the processing state could not be updated.
-         */
-        public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1;
-
-
-        /**
-         * @hide
-         */
-        @IntDef(value = {
-                PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
-        }, open = true)
-        @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
-                ElementType.FIELD})
-        @Retention(RetentionPolicy.SOURCE)
-        public @interface ErrorCode {
-        }
-
-        public OnDeviceUpdateProcessingException(@ErrorCode int errorCode) {
-            super(errorCode);
-        }
-
-        public OnDeviceUpdateProcessingException(@ErrorCode int errorCode,
-                @NonNull String errorMessage) {
-            super(errorCode, errorMessage);
-        }
-    }
-
-    /**
-     * Exception type to be used for surfacing errors to service implementation.
-     */
-    public abstract static class OnDeviceIntelligenceServiceException extends Exception {
-        private final int mErrorCode;
-
-        public OnDeviceIntelligenceServiceException(int errorCode) {
-            this.mErrorCode = errorCode;
-        }
-
-        public OnDeviceIntelligenceServiceException(int errorCode,
-                @NonNull String errorMessage) {
-            super(errorMessage);
-            this.mErrorCode = errorCode;
-        }
-
-        public int getErrorCode() {
-            return mErrorCode;
-        }
-
-    }
 }
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 7f7f9c2..d943c80 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -27,20 +27,21 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.Service;
-import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
+import android.os.Bundle;
 import android.app.ondeviceintelligence.Feature;
 import android.app.ondeviceintelligence.IProcessingSignal;
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
 import android.app.ondeviceintelligence.ITokenInfoCallback;
 import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
 import android.app.ondeviceintelligence.ProcessingSignal;
-import android.app.ondeviceintelligence.ProcessingOutcomeReceiver;
-import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.ProcessingCallback;
+import android.app.ondeviceintelligence.StreamingProcessingCallback;
 import android.app.ondeviceintelligence.TokenInfo;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -51,7 +52,6 @@
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
-import android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException;
 import android.util.Log;
 import android.util.Slog;
 
@@ -121,7 +121,7 @@
                 }
 
                 @Override
-                public void requestTokenInfo(int callerUid, Feature feature, Content request,
+                public void requestTokenInfo(int callerUid, Feature feature, Bundle request,
                         ICancellationSignal cancellationSignal,
                         ITokenInfoCallback tokenInfoCallback) {
                     Objects.requireNonNull(feature);
@@ -134,7 +134,7 @@
                 }
 
                 @Override
-                public void processRequestStreaming(int callerUid, Feature feature, Content request,
+                public void processRequestStreaming(int callerUid, Feature feature, Bundle request,
                         int requestType, ICancellationSignal cancellationSignal,
                         IProcessingSignal processingSignal,
                         IStreamingResponseCallback callback) {
@@ -151,7 +151,7 @@
                 }
 
                 @Override
-                public void processRequest(int callerUid, Feature feature, Content request,
+                public void processRequest(int callerUid, Feature feature, Bundle request,
                         int requestType, ICancellationSignal cancellationSignal,
                         IProcessingSignal processingSignal,
                         IResponseCallback callback) {
@@ -198,18 +198,17 @@
     @NonNull
     public abstract void onTokenInfoRequest(
             int callerUid, @NonNull Feature feature,
-            @NonNull Content request,
+            @NonNull @InferenceParams Bundle request,
             @Nullable CancellationSignal cancellationSignal,
-            @NonNull OutcomeReceiver<TokenInfo,
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+            @NonNull OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback);
 
     /**
      * Invoked when caller provides a request for a particular feature to be processed in a
      * streaming manner. The expectation from the implementation is that when processing the
      * request,
-     * it periodically populates the {@link StreamedProcessingOutcomeReceiver#onNewContent} to continuously
-     * provide partial Content results for the caller to utilize. Optionally the implementation can
-     * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon
+     * it periodically populates the {@link StreamingProcessingCallback#onPartialResult} to continuously
+     * provide partial Bundle results for the caller to utilize. Optionally the implementation can
+     * provide the complete response in the {@link StreamingProcessingCallback#onResult} upon
      * processing completion.
      *
      * @param callerUid          UID of the caller that initiated this call chain.
@@ -225,11 +224,11 @@
     @NonNull
     public abstract void onProcessRequestStreaming(
             int callerUid, @NonNull Feature feature,
-            @Nullable Content request,
+            @NonNull @InferenceParams Bundle request,
             @OnDeviceIntelligenceManager.RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
-            @NonNull StreamedProcessingOutcomeReceiver callback);
+            @NonNull StreamingProcessingCallback callback);
 
     /**
      * Invoked when caller provides a request for a particular feature to be processed in one shot
@@ -251,11 +250,11 @@
     @NonNull
     public abstract void onProcessRequest(
             int callerUid, @NonNull Feature feature,
-            @Nullable Content request,
+            @NonNull @InferenceParams Bundle request,
             @OnDeviceIntelligenceManager.RequestType int requestType,
             @Nullable CancellationSignal cancellationSignal,
             @Nullable ProcessingSignal processingSignal,
-            @NonNull ProcessingOutcomeReceiver callback);
+            @NonNull ProcessingCallback callback);
 
 
     /**
@@ -267,9 +266,9 @@
      * @param callback        callback to populate the update status and if there are params
      *                        associated with the status.
      */
-    public abstract void onUpdateProcessingState(@NonNull Bundle processingState,
+    public abstract void onUpdateProcessingState(@NonNull @InferenceParams Bundle processingState,
             @NonNull OutcomeReceiver<PersistableBundle,
-                    OnDeviceUpdateProcessingException> callback);
+                    OnDeviceIntelligenceException> callback);
 
 
     /**
@@ -344,7 +343,7 @@
     /**
      * Returns the {@link Executor} to use for incoming IPC from request sender into your service
      * implementation. For e.g. see
-     * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content,
+     * {@link ProcessingCallback#onDataAugmentRequest(Bundle,
      * Consumer)} where we use the executor to populate the consumer.
      * <p>
      * Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
@@ -380,13 +379,13 @@
         });
     }
 
-    private ProcessingOutcomeReceiver wrapResponseCallback(
+    private ProcessingCallback wrapResponseCallback(
             IResponseCallback callback) {
-        return new ProcessingOutcomeReceiver() {
+        return new ProcessingCallback() {
             @Override
-            public void onResult(@androidx.annotation.NonNull Content response) {
+            public void onResult(@androidx.annotation.NonNull Bundle result) {
                 try {
-                    callback.onSuccess(response);
+                    callback.onSuccess(result);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error sending result: " + e);
                 }
@@ -394,7 +393,7 @@
 
             @Override
             public void onError(
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+                    OnDeviceIntelligenceException exception) {
                 try {
                     callback.onFailure(exception.getErrorCode(), exception.getMessage(),
                             exception.getErrorParams());
@@ -404,8 +403,8 @@
             }
 
             @Override
-            public void onDataAugmentRequest(@NonNull Content content,
-                    @NonNull Consumer<Content> contentCallback) {
+            public void onDataAugmentRequest(@NonNull Bundle content,
+                    @NonNull Consumer<Bundle> contentCallback) {
                 try {
                     callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
 
@@ -416,22 +415,22 @@
         };
     }
 
-    private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback(
+    private StreamingProcessingCallback wrapStreamingResponseCallback(
             IStreamingResponseCallback callback) {
-        return new StreamedProcessingOutcomeReceiver() {
+        return new StreamingProcessingCallback() {
             @Override
-            public void onNewContent(@androidx.annotation.NonNull Content content) {
+            public void onPartialResult(@androidx.annotation.NonNull Bundle partialResult) {
                 try {
-                    callback.onNewContent(content);
+                    callback.onNewContent(partialResult);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error sending result: " + e);
                 }
             }
 
             @Override
-            public void onResult(@androidx.annotation.NonNull Content response) {
+            public void onResult(@androidx.annotation.NonNull Bundle result) {
                 try {
-                    callback.onSuccess(response);
+                    callback.onSuccess(result);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error sending result: " + e);
                 }
@@ -439,7 +438,7 @@
 
             @Override
             public void onError(
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+                    OnDeviceIntelligenceException exception) {
                 try {
                     callback.onFailure(exception.getErrorCode(), exception.getMessage(),
                             exception.getErrorParams());
@@ -449,8 +448,8 @@
             }
 
             @Override
-            public void onDataAugmentRequest(@NonNull Content content,
-                    @NonNull Consumer<Content> contentCallback) {
+            public void onDataAugmentRequest(@NonNull Bundle content,
+                    @NonNull Consumer<Bundle> contentCallback) {
                 try {
                     callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
 
@@ -462,13 +461,13 @@
     }
 
     private RemoteCallback wrapRemoteCallback(
-            @androidx.annotation.NonNull Consumer<Content> contentCallback) {
+            @androidx.annotation.NonNull Consumer<Bundle> contentCallback) {
         return new RemoteCallback(
                 result -> {
                     if (result != null) {
                         getCallbackExecutor().execute(() -> contentCallback.accept(
                                 result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
-                                        Content.class)));
+                                        Bundle.class)));
                     } else {
                         getCallbackExecutor().execute(
                                 () -> contentCallback.accept(null));
@@ -476,8 +475,7 @@
                 });
     }
 
-    private OutcomeReceiver<TokenInfo,
-            OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+    private OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> wrapTokenInfoCallback(
             ITokenInfoCallback tokenInfoCallback) {
         return new OutcomeReceiver<>() {
             @Override
@@ -491,7 +489,7 @@
 
             @Override
             public void onError(
-                    OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+                    OnDeviceIntelligenceException exception) {
                 try {
                     tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
                             exception.getErrorParams());
@@ -503,7 +501,7 @@
     }
 
     @NonNull
-    private static OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> wrapOutcomeReceiver(
+    private static OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> wrapOutcomeReceiver(
             IProcessingUpdateStatusCallback callback) {
         return new OutcomeReceiver<>() {
             @Override
@@ -518,7 +516,7 @@
 
             @Override
             public void onError(
-                    @androidx.annotation.NonNull OnDeviceUpdateProcessingException error) {
+                    @androidx.annotation.NonNull OnDeviceIntelligenceException error) {
                 try {
                     callback.onFailure(error.getErrorCode(), error.getMessage());
                 } catch (RemoteException e) {
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 9db8aa1..e95b216 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -28,6 +28,7 @@
 import android.graphics.Path;
 import android.graphics.RectF;
 import android.graphics.text.LineBreakConfig;
+import android.os.Trace;
 import android.text.style.ParagraphStyle;
 
 /**
@@ -567,49 +568,59 @@
     public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
             @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
             @Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) {
-        final int textLength = text.length();
-        if (hasAnyInterestingChars(text, textLength)) {
-           return null;  // There are some interesting characters. Not boring.
+        if (TRACE_LAYOUT) {
+            Trace.beginSection("BoringLayout#isBoring");
+            Trace.setCounter("BoringLayout#textLength", text.length());
         }
-        if (textDir != null && textDir.isRtl(text, 0, textLength)) {
-           return null;  // The heuristic considers the whole text RTL. Not boring.
-        }
-        if (text instanceof Spanned) {
-            Spanned sp = (Spanned) text;
-            Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
-            if (styles.length > 0) {
-                return null;  // There are some ParagraphStyle spans. Not boring.
+        try {
+            final int textLength = text.length();
+            if (hasAnyInterestingChars(text, textLength)) {
+                return null;  // There are some interesting characters. Not boring.
+            }
+            if (textDir != null && textDir.isRtl(text, 0, textLength)) {
+                return null;  // The heuristic considers the whole text RTL. Not boring.
+            }
+            if (text instanceof Spanned) {
+                Spanned sp = (Spanned) text;
+                Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
+                if (styles.length > 0) {
+                    return null;  // There are some ParagraphStyle spans. Not boring.
+                }
+            }
+
+            Metrics fm = metrics;
+            if (fm == null) {
+                fm = new Metrics();
+            } else {
+                fm.reset();
+            }
+
+            if (ClientFlags.fixLineHeightForLocale()) {
+                if (minimumFontMetrics != null) {
+                    fm.set(minimumFontMetrics);
+                    // Because the font metrics is provided by public APIs, adjust the top/bottom
+                    // with ascent/descent: top must be smaller than ascent, bottom must be larger
+                    // than descent.
+                    fm.top = Math.min(fm.top, fm.ascent);
+                    fm.bottom = Math.max(fm.bottom, fm.descent);
+                }
+            }
+
+            TextLine line = TextLine.obtain();
+            line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
+                    Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
+                    0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
+                    0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
+                    useFallbackLineSpacing);
+            fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
+            TextLine.recycle(line);
+
+            return fm;
+        } finally {
+            if (TRACE_LAYOUT) {
+                Trace.endSection();
             }
         }
-
-        Metrics fm = metrics;
-        if (fm == null) {
-            fm = new Metrics();
-        } else {
-            fm.reset();
-        }
-
-        if (ClientFlags.fixLineHeightForLocale()) {
-            if (minimumFontMetrics != null) {
-                fm.set(minimumFontMetrics);
-                // Because the font metrics is provided by public APIs, adjust the top/bottom with
-                // ascent/descent: top must be smaller than ascent, bottom must be larger than
-                // descent.
-                fm.top = Math.min(fm.top, fm.ascent);
-                fm.bottom = Math.max(fm.bottom, fm.descent);
-            }
-        }
-
-        TextLine line = TextLine.obtain();
-        line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
-                Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
-                0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
-                0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
-                useFallbackLineSpacing);
-        fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
-        TextLine.recycle(line);
-
-        return fm;
     }
 
     @Override
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index cce4f7b..99ce0ef 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -31,6 +31,7 @@
 import android.graphics.Rect;
 import android.graphics.text.LineBreakConfig;
 import android.os.Build;
+import android.os.Trace;
 import android.text.method.OffsetMapping;
 import android.text.style.ReplacementSpan;
 import android.text.style.UpdateLayout;
@@ -636,207 +637,224 @@
     /** @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void reflow(CharSequence s, int where, int before, int after) {
-        if (s != mBase)
-            return;
-
-        CharSequence text = mDisplay;
-        int len = text.length();
-
-        // seek back to the start of the paragraph
-
-        int find = TextUtils.lastIndexOf(text, '\n', where - 1);
-        if (find < 0)
-            find = 0;
-        else
-            find = find + 1;
-
-        {
-            int diff = where - find;
-            before += diff;
-            after += diff;
-            where -= diff;
+        if (TRACE_LAYOUT) {
+            Trace.beginSection("DynamicLayout#reflow");
         }
-
-        // seek forward to the end of the paragraph
-
-        int look = TextUtils.indexOf(text, '\n', where + after);
-        if (look < 0)
-            look = len;
-        else
-            look++; // we want the index after the \n
-
-        int change = look - (where + after);
-        before += change;
-        after += change;
-
-        // seek further out to cover anything that is forced to wrap together
-
-        if (text instanceof Spanned) {
-            Spanned sp = (Spanned) text;
-            boolean again;
-
-            do {
-                again = false;
-
-                Object[] force = sp.getSpans(where, where + after,
-                                             WrapTogetherSpan.class);
-
-                for (int i = 0; i < force.length; i++) {
-                    int st = sp.getSpanStart(force[i]);
-                    int en = sp.getSpanEnd(force[i]);
-
-                    if (st < where) {
-                        again = true;
-
-                        int diff = where - st;
-                        before += diff;
-                        after += diff;
-                        where -= diff;
-                    }
-
-                    if (en > where + after) {
-                        again = true;
-
-                        int diff = en - (where + after);
-                        before += diff;
-                        after += diff;
-                    }
-                }
-            } while (again);
-        }
-
-        // find affected region of old layout
-
-        int startline = getLineForOffset(where);
-        int startv = getLineTop(startline);
-
-        int endline = getLineForOffset(where + before);
-        if (where + after == len)
-            endline = getLineCount();
-        int endv = getLineTop(endline);
-        boolean islast = (endline == getLineCount());
-
-        // generate new layout for affected text
-
-        StaticLayout reflowed;
-        StaticLayout.Builder b;
-
-        synchronized (sLock) {
-            reflowed = sStaticLayout;
-            b = sBuilder;
-            sStaticLayout = null;
-            sBuilder = null;
-        }
-
-        if (b == null) {
-            b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
-        }
-
-        b.setText(text, where, where + after)
-                .setPaint(getPaint())
-                .setWidth(getWidth())
-                .setTextDirection(getTextDirectionHeuristic())
-                .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
-                .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
-                .setEllipsizedWidth(mEllipsizedWidth)
-                .setEllipsize(mEllipsizeAt)
-                .setBreakStrategy(mBreakStrategy)
-                .setHyphenationFrequency(mHyphenationFrequency)
-                .setJustificationMode(mJustificationMode)
-                .setLineBreakConfig(mLineBreakConfig)
-                .setAddLastLineLineSpacing(!islast)
-                .setIncludePad(false)
-                .setUseBoundsForWidth(mUseBoundsForWidth)
-                .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
-                .setMinimumFontMetrics(mMinimumFontMetrics)
-                .setCalculateBounds(true);
-
-        reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
-        int n = reflowed.getLineCount();
-        // If the new layout has a blank line at the end, but it is not
-        // the very end of the buffer, then we already have a line that
-        // starts there, so disregard the blank line.
-
-        if (where + after != len && reflowed.getLineStart(n - 1) == where + after)
-            n--;
-
-        // remove affected lines from old layout
-        mInts.deleteAt(startline, endline - startline);
-        mObjects.deleteAt(startline, endline - startline);
-
-        // adjust offsets in layout for new height and offsets
-
-        int ht = reflowed.getLineTop(n);
-        int toppad = 0, botpad = 0;
-
-        if (mIncludePad && startline == 0) {
-            toppad = reflowed.getTopPadding();
-            mTopPadding = toppad;
-            ht -= toppad;
-        }
-        if (mIncludePad && islast) {
-            botpad = reflowed.getBottomPadding();
-            mBottomPadding = botpad;
-            ht += botpad;
-        }
-
-        mInts.adjustValuesBelow(startline, START, after - before);
-        mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
-
-        // insert new layout
-
-        int[] ints;
-
-        if (mEllipsize) {
-            ints = new int[COLUMNS_ELLIPSIZE];
-            ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
-        } else {
-            ints = new int[COLUMNS_NORMAL];
-        }
-
-        Directions[] objects = new Directions[1];
-
-        for (int i = 0; i < n; i++) {
-            final int start = reflowed.getLineStart(i);
-            ints[START] = start;
-            ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
-            ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
-
-            int top = reflowed.getLineTop(i) + startv;
-            if (i > 0)
-                top -= toppad;
-            ints[TOP] = top;
-
-            int desc = reflowed.getLineDescent(i);
-            if (i == n - 1)
-                desc += botpad;
-
-            ints[DESCENT] = desc;
-            ints[EXTRA] = reflowed.getLineExtra(i);
-            objects[0] = reflowed.getLineDirections(i);
-
-            final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
-            ints[HYPHEN] = StaticLayout.packHyphenEdit(
-                    reflowed.getStartHyphenEdit(i), reflowed.getEndHyphenEdit(i));
-            ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
-                    contentMayProtrudeFromLineTopOrBottom(text, start, end) ?
-                            MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
-
-            if (mEllipsize) {
-                ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
-                ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
+        try {
+            if (s != mBase) {
+                return;
             }
 
-            mInts.insertAt(startline + i, ints);
-            mObjects.insertAt(startline + i, objects);
-        }
+            CharSequence text = mDisplay;
+            int len = text.length();
 
-        updateBlocks(startline, endline - 1, n);
+            // seek back to the start of the paragraph
 
-        b.finish();
-        synchronized (sLock) {
-            sStaticLayout = reflowed;
-            sBuilder = b;
+            int find = TextUtils.lastIndexOf(text, '\n', where - 1);
+            if (find < 0) {
+                find = 0;
+            } else {
+                find = find + 1;
+            }
+
+            {
+                int diff = where - find;
+                before += diff;
+                after += diff;
+                where -= diff;
+            }
+
+            // seek forward to the end of the paragraph
+
+            int look = TextUtils.indexOf(text, '\n', where + after);
+            if (look < 0) {
+                look = len;
+            } else {
+                look++; // we want the index after the \n
+            }
+
+            int change = look - (where + after);
+            before += change;
+            after += change;
+
+            // seek further out to cover anything that is forced to wrap together
+
+            if (text instanceof Spanned) {
+                Spanned sp = (Spanned) text;
+                boolean again;
+
+                do {
+                    again = false;
+
+                    Object[] force = sp.getSpans(where, where + after,
+                            WrapTogetherSpan.class);
+
+                    for (int i = 0; i < force.length; i++) {
+                        int st = sp.getSpanStart(force[i]);
+                        int en = sp.getSpanEnd(force[i]);
+
+                        if (st < where) {
+                            again = true;
+
+                            int diff = where - st;
+                            before += diff;
+                            after += diff;
+                            where -= diff;
+                        }
+
+                        if (en > where + after) {
+                            again = true;
+
+                            int diff = en - (where + after);
+                            before += diff;
+                            after += diff;
+                        }
+                    }
+                } while (again);
+            }
+
+            // find affected region of old layout
+
+            int startline = getLineForOffset(where);
+            int startv = getLineTop(startline);
+
+            int endline = getLineForOffset(where + before);
+            if (where + after == len) {
+                endline = getLineCount();
+            }
+            int endv = getLineTop(endline);
+            boolean islast = (endline == getLineCount());
+
+            // generate new layout for affected text
+
+            StaticLayout reflowed;
+            StaticLayout.Builder b;
+
+            synchronized (sLock) {
+                reflowed = sStaticLayout;
+                b = sBuilder;
+                sStaticLayout = null;
+                sBuilder = null;
+            }
+
+            if (b == null) {
+                b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
+            }
+
+            b.setText(text, where, where + after)
+                    .setPaint(getPaint())
+                    .setWidth(getWidth())
+                    .setTextDirection(getTextDirectionHeuristic())
+                    .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
+                    .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
+                    .setEllipsizedWidth(mEllipsizedWidth)
+                    .setEllipsize(mEllipsizeAt)
+                    .setBreakStrategy(mBreakStrategy)
+                    .setHyphenationFrequency(mHyphenationFrequency)
+                    .setJustificationMode(mJustificationMode)
+                    .setLineBreakConfig(mLineBreakConfig)
+                    .setAddLastLineLineSpacing(!islast)
+                    .setIncludePad(false)
+                    .setUseBoundsForWidth(mUseBoundsForWidth)
+                    .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
+                    .setMinimumFontMetrics(mMinimumFontMetrics)
+                    .setCalculateBounds(true);
+
+            reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */,
+                    reflowed);
+            int n = reflowed.getLineCount();
+            // If the new layout has a blank line at the end, but it is not
+            // the very end of the buffer, then we already have a line that
+            // starts there, so disregard the blank line.
+
+            if (where + after != len && reflowed.getLineStart(n - 1) == where + after) {
+                n--;
+            }
+
+            // remove affected lines from old layout
+            mInts.deleteAt(startline, endline - startline);
+            mObjects.deleteAt(startline, endline - startline);
+
+            // adjust offsets in layout for new height and offsets
+
+            int ht = reflowed.getLineTop(n);
+            int toppad = 0, botpad = 0;
+
+            if (mIncludePad && startline == 0) {
+                toppad = reflowed.getTopPadding();
+                mTopPadding = toppad;
+                ht -= toppad;
+            }
+            if (mIncludePad && islast) {
+                botpad = reflowed.getBottomPadding();
+                mBottomPadding = botpad;
+                ht += botpad;
+            }
+
+            mInts.adjustValuesBelow(startline, START, after - before);
+            mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
+
+            // insert new layout
+
+            int[] ints;
+
+            if (mEllipsize) {
+                ints = new int[COLUMNS_ELLIPSIZE];
+                ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
+            } else {
+                ints = new int[COLUMNS_NORMAL];
+            }
+
+            Directions[] objects = new Directions[1];
+
+            for (int i = 0; i < n; i++) {
+                final int start = reflowed.getLineStart(i);
+                ints[START] = start;
+                ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
+                ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
+
+                int top = reflowed.getLineTop(i) + startv;
+                if (i > 0) {
+                    top -= toppad;
+                }
+                ints[TOP] = top;
+
+                int desc = reflowed.getLineDescent(i);
+                if (i == n - 1) {
+                    desc += botpad;
+                }
+
+                ints[DESCENT] = desc;
+                ints[EXTRA] = reflowed.getLineExtra(i);
+                objects[0] = reflowed.getLineDirections(i);
+
+                final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
+                ints[HYPHEN] = StaticLayout.packHyphenEdit(
+                        reflowed.getStartHyphenEdit(i), reflowed.getEndHyphenEdit(i));
+                ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
+                        contentMayProtrudeFromLineTopOrBottom(text, start, end)
+                                ? MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
+
+                if (mEllipsize) {
+                    ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
+                    ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
+                }
+
+                mInts.insertAt(startline + i, ints);
+                mObjects.insertAt(startline + i, objects);
+            }
+
+            updateBlocks(startline, endline - 1, n);
+
+            b.finish();
+            synchronized (sLock) {
+                sStaticLayout = reflowed;
+                sBuilder = b;
+            }
+        } finally {
+            if (TRACE_LAYOUT) {
+                Trace.endSection();
+            }
         }
     }
 
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8dee4b1..ce238a7 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -39,6 +39,7 @@
 import android.graphics.text.LineBreakConfig;
 import android.graphics.text.LineBreaker;
 import android.os.Build;
+import android.os.Trace;
 import android.text.method.TextKeyListener;
 import android.text.style.AlignmentSpan;
 import android.text.style.LeadingMarginSpan;
@@ -70,6 +71,11 @@
  * For text that will not change, use a {@link StaticLayout}.
  */
 public abstract class Layout {
+
+    /** @hide */
+    protected static final boolean TRACE_LAYOUT = Build.isDebuggable();
+
+
     /** @hide */
     @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
             LineBreaker.BREAK_STRATEGY_SIMPLE,
@@ -472,40 +478,51 @@
             @Nullable Path selectionPath,
             @Nullable Paint selectionPaint,
             int cursorOffsetVertical) {
-        float leftShift = 0;
-        if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) {
-            RectF drawingRect = computeDrawingBoundingBox();
-            if (drawingRect.left < 0) {
-                leftShift = -drawingRect.left;
-                canvas.translate(leftShift, 0);
+        if (TRACE_LAYOUT) {
+            Trace.beginSection("Layout#draw");
+        }
+        try {
+            float leftShift = 0;
+            if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) {
+                RectF drawingRect = computeDrawingBoundingBox();
+                if (drawingRect.left < 0) {
+                    leftShift = -drawingRect.left;
+                    canvas.translate(leftShift, 0);
+                }
             }
-        }
-        final long lineRange = getLineRangeForDraw(canvas);
-        int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
-        int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
-        if (lastLine < 0) return;
+            final long lineRange = getLineRangeForDraw(canvas);
+            int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+            int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+            if (lastLine < 0) return;
 
-        if (shouldDrawHighlightsOnTop(canvas)) {
-            drawBackground(canvas, firstLine, lastLine);
-        } else {
-            drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
-                    cursorOffsetVertical, firstLine, lastLine);
-        }
+            if (shouldDrawHighlightsOnTop(canvas)) {
+                drawBackground(canvas, firstLine, lastLine);
+            } else {
+                drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath,
+                        selectionPaint,
+                        cursorOffsetVertical, firstLine, lastLine);
+            }
 
-        drawText(canvas, firstLine, lastLine);
+            drawText(canvas, firstLine, lastLine);
 
-        // Since high contrast text draws a solid rectangle background behind the text, it covers up
-        // the highlights and selections. In this case we draw over the top of the text with a
-        // blend mode that ensures the text stays high-contrast.
-        if (shouldDrawHighlightsOnTop(canvas)) {
-            drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
-                    cursorOffsetVertical, firstLine, lastLine);
-        }
+            // Since high contrast text draws a solid rectangle background behind the text, it
+            // covers up the highlights and selections. In this case we draw over the top of the
+            // text with a blend mode that ensures the text stays high-contrast.
+            if (shouldDrawHighlightsOnTop(canvas)) {
+                drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath,
+                        selectionPaint,
+                        cursorOffsetVertical, firstLine, lastLine);
+            }
 
-        if (leftShift != 0) {
-            // Manually translate back to the original position because of b/324498002, using
-            // save/restore disappears the toggle switch drawables.
-            canvas.translate(-leftShift, 0);
+            if (leftShift != 0) {
+                // Manually translate back to the original position because of b/324498002, using
+                // save/restore disappears the toggle switch drawables.
+                canvas.translate(-leftShift, 0);
+            }
+        } finally {
+            if (TRACE_LAYOUT) {
+                Trace.endSection();
+            }
         }
     }
 
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 5f6a9bd..14401a6 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -25,6 +25,8 @@
 import android.graphics.Rect;
 import android.graphics.text.LineBreakConfig;
 import android.graphics.text.MeasuredText;
+import android.os.Build;
+import android.os.Trace;
 import android.text.style.MetricAffectingSpan;
 
 import com.android.internal.util.Preconditions;
@@ -78,6 +80,8 @@
 public class PrecomputedText implements Spannable {
     private static final char LINE_FEED = '\n';
 
+    private static final boolean TRACE_PCT = Build.isDebuggable();
+
     /**
      * The information required for building {@link PrecomputedText}.
      *
@@ -447,35 +451,47 @@
 
     private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText(
             @NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) {
-        final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
-                && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
-        final int hyphenationMode;
-        if (needHyphenation) {
-            hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
-                    ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
-                    MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
-        } else {
-            hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+        if (TRACE_PCT) {
+            Trace.beginSection("PrecomputedText#createMeasuredParagraphsFromPrecomputedText");
+            Trace.setCounter("PrecomputedText#textCharCount", pct.length());
         }
-        LineBreakConfig config = params.getLineBreakConfig();
-        if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
-                && pct.getParagraphCount() != 1) {
-            // If the text has multiple paragraph, resolve line break word style auto to none.
-            config = new LineBreakConfig.Builder()
-                    .merge(config)
-                    .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
-                    .build();
+        try {
+            final boolean needHyphenation =
+                    params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+                            && params.getHyphenationFrequency()
+                            != Layout.HYPHENATION_FREQUENCY_NONE;
+            final int hyphenationMode;
+            if (needHyphenation) {
+                hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+                        ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+                        MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
+            } else {
+                hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+            }
+            LineBreakConfig config = params.getLineBreakConfig();
+            if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+                    && pct.getParagraphCount() != 1) {
+                // If the text has multiple paragraph, resolve line break word style auto to none.
+                config = new LineBreakConfig.Builder()
+                        .merge(config)
+                        .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                        .build();
+            }
+            ArrayList<ParagraphInfo> result = new ArrayList<>();
+            for (int i = 0; i < pct.getParagraphCount(); ++i) {
+                final int paraStart = pct.getParagraphStart(i);
+                final int paraEnd = pct.getParagraphEnd(i);
+                result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+                        params.getTextPaint(), config, pct, paraStart, paraEnd,
+                        params.getTextDirection(), hyphenationMode, computeLayout, true,
+                        pct.getMeasuredParagraph(i), null /* no recycle */)));
+            }
+            return result.toArray(new ParagraphInfo[result.size()]);
+        } finally {
+            if (TRACE_PCT) {
+                Trace.endSection();
+            }
         }
-        ArrayList<ParagraphInfo> result = new ArrayList<>();
-        for (int i = 0; i < pct.getParagraphCount(); ++i) {
-            final int paraStart = pct.getParagraphStart(i);
-            final int paraEnd = pct.getParagraphEnd(i);
-            result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
-                    params.getTextPaint(), config, pct, paraStart, paraEnd,
-                    params.getTextDirection(), hyphenationMode, computeLayout, true,
-                    pct.getMeasuredParagraph(i), null /* no recycle */)));
-        }
-        return result.toArray(new ParagraphInfo[result.size()]);
     }
 
     /** @hide */
@@ -483,53 +499,65 @@
             @NonNull CharSequence text, @NonNull Params params,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean computeLayout,
             boolean computeBounds) {
-        ArrayList<ParagraphInfo> result = new ArrayList<>();
-
-        Preconditions.checkNotNull(text);
-        Preconditions.checkNotNull(params);
-        final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
-                && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
-        final int hyphenationMode;
-        if (needHyphenation) {
-            hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
-                    ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
-                    MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
-        } else {
-            hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+        if (TRACE_PCT) {
+            Trace.beginSection("PrecomputedText#createMeasuredParagraphs");
+            Trace.setCounter("PrecomputedText#textCharCount", text.length());
         }
+        try {
+            ArrayList<ParagraphInfo> result = new ArrayList<>();
 
-        LineBreakConfig config = null;
-        int paraEnd = 0;
-        for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
-            paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
-            if (paraEnd < 0) {
-                // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
-                // end.
-                paraEnd = end;
+            Preconditions.checkNotNull(text);
+            Preconditions.checkNotNull(params);
+            final boolean needHyphenation =
+                    params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+                            && params.getHyphenationFrequency()
+                            != Layout.HYPHENATION_FREQUENCY_NONE;
+            final int hyphenationMode;
+            if (needHyphenation) {
+                hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+                        ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+                        MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
             } else {
-                paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
+                hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
             }
 
-            if (config == null) {
-                config = params.getLineBreakConfig();
-                if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
-                        && !(paraStart == start && paraEnd == end)) {
-                    // If the text has multiple paragraph, resolve line break word style auto to
-                    // none.
-                    config = new LineBreakConfig.Builder()
-                            .merge(config)
-                            .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
-                            .build();
+            LineBreakConfig config = null;
+            int paraEnd = 0;
+            for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
+                paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
+                if (paraEnd < 0) {
+                    // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
+                    // end.
+                    paraEnd = end;
+                } else {
+                    paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
                 }
-            }
 
-            result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
-                    params.getTextPaint(), config, text, paraStart, paraEnd,
-                    params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
-                    null /* no hint */,
-                    null /* no recycle */)));
+                if (config == null) {
+                    config = params.getLineBreakConfig();
+                    if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+                            && !(paraStart == start && paraEnd == end)) {
+                        // If the text has multiple paragraph, resolve line break word style auto to
+                        // none.
+                        config = new LineBreakConfig.Builder()
+                                .merge(config)
+                                .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                                .build();
+                    }
+                }
+
+                result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+                        params.getTextPaint(), config, text, paraStart, paraEnd,
+                        params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
+                        null /* no hint */,
+                        null /* no recycle */)));
+            }
+            return result.toArray(new ParagraphInfo[result.size()]);
+        } finally {
+            if (TRACE_PCT) {
+                Trace.endSection();
+            }
         }
-        return result.toArray(new ParagraphInfo[result.size()]);
     }
 
     // Use PrecomputedText.create instead.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3dd3a9e..d1b14d1 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -542,10 +542,20 @@
          */
         @NonNull
         public StaticLayout build() {
-            StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
-                    ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
-            Builder.recycle(this);
-            return result;
+            if (TRACE_LAYOUT) {
+                Trace.beginSection("StaticLayout#build");
+                Trace.setCounter("StaticLayout#textLength", mText.length());
+            }
+            try {
+                StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
+                        ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
+                Builder.recycle(this);
+                return result;
+            } finally {
+                if (TRACE_LAYOUT) {
+                    Trace.endSection();
+                }
+            }
         }
 
         /**
@@ -562,16 +572,21 @@
          */
         /* package */ @NonNull StaticLayout buildPartialStaticLayoutForDynamicLayout(
                 boolean trackpadding, StaticLayout recycle) {
-            if (recycle == null) {
-                recycle = new StaticLayout();
+            if (TRACE_LAYOUT) {
+                Trace.beginSection("StaticLayout#forDynamicLayout");
+                Trace.setCounter("StaticLayout#textLength", mText.length());
             }
-            Trace.beginSection("Generating StaticLayout For DynamicLayout");
             try {
+                if (recycle == null) {
+                    recycle = new StaticLayout();
+                }
                 recycle.generate(this, mIncludePad, trackpadding);
+                return recycle;
             } finally {
-                Trace.endSection();
+                if (TRACE_LAYOUT) {
+                    Trace.endSection();
+                }
             }
-            return recycle;
         }
 
         private CharSequence mText;
@@ -727,12 +742,7 @@
         mLeftIndents = b.mLeftIndents;
         mRightIndents = b.mRightIndents;
 
-        Trace.beginSection("Constructing StaticLayout");
-        try {
-            generate(b, b.mIncludePad, trackPadding);
-        } finally {
-            Trace.endSection();
-        }
+        generate(b, b.mIncludePad, trackPadding);
     }
 
     private static int getBaseHyphenationFrequency(int frequency) {
@@ -842,14 +852,23 @@
                 case PrecomputedText.Params.UNUSABLE:
                     break;
                 case PrecomputedText.Params.NEED_RECOMPUTE:
-                    final PrecomputedText.Params newParams =
-                            new PrecomputedText.Params.Builder(paint)
-                                .setBreakStrategy(b.mBreakStrategy)
-                                .setHyphenationFrequency(b.mHyphenationFrequency)
-                                .setTextDirection(textDir)
-                                .setLineBreakConfig(b.mLineBreakConfig)
-                                .build();
-                    precomputed = PrecomputedText.create(precomputed, newParams);
+                    if (TRACE_LAYOUT) {
+                        Trace.beginSection("StaticLayout#recomputePct");
+                    }
+                    try {
+                        final PrecomputedText.Params newParams =
+                                new PrecomputedText.Params.Builder(paint)
+                                    .setBreakStrategy(b.mBreakStrategy)
+                                    .setHyphenationFrequency(b.mHyphenationFrequency)
+                                    .setTextDirection(textDir)
+                                    .setLineBreakConfig(b.mLineBreakConfig)
+                                    .build();
+                        precomputed = PrecomputedText.create(precomputed, newParams);
+                    } finally {
+                        if (TRACE_LAYOUT) {
+                            Trace.endSection();
+                        }
+                    }
                     paragraphInfo = precomputed.getParagraphInfo();
                     break;
                 case PrecomputedText.Params.USABLE:
@@ -860,232 +879,261 @@
         }
 
         if (paragraphInfo == null) {
-            final PrecomputedText.Params param = new PrecomputedText.Params(paint,
-                    b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
-            paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
-                    bufEnd, false /* computeLayout */, b.mCalculateBounds);
+            if (TRACE_LAYOUT) {
+                Trace.beginSection("StaticLayout#computePct");
+            }
+            try {
+                final PrecomputedText.Params param = new PrecomputedText.Params(paint,
+                        b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
+                paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
+                        bufEnd, false /* computeLayout */, b.mCalculateBounds);
+            } finally {
+                if (TRACE_LAYOUT) {
+                    Trace.endSection();
+                }
+            }
         }
 
         for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
-            final int paraStart = paraIndex == 0
-                    ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
-            final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
+            if (TRACE_LAYOUT) {
+                Trace.beginSection("StaticLayout#processParagraph");
+                Trace.setCounter("StaticLayout#paragraph", paraIndex);
+            }
+            try {
+                final int paraStart = paraIndex == 0
+                        ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
+                final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
 
-            int firstWidthLineCount = 1;
-            int firstWidth = outerWidth;
-            int restWidth = outerWidth;
+                int firstWidthLineCount = 1;
+                int firstWidth = outerWidth;
+                int restWidth = outerWidth;
 
-            LineHeightSpan[] chooseHt = null;
-            if (spanned != null) {
-                LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
-                        LeadingMarginSpan.class);
-                for (int i = 0; i < sp.length; i++) {
-                    LeadingMarginSpan lms = sp[i];
-                    firstWidth -= sp[i].getLeadingMargin(true);
-                    restWidth -= sp[i].getLeadingMargin(false);
+                LineHeightSpan[] chooseHt = null;
+                if (spanned != null) {
+                    LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+                            LeadingMarginSpan.class);
+                    for (int i = 0; i < sp.length; i++) {
+                        LeadingMarginSpan lms = sp[i];
+                        firstWidth -= sp[i].getLeadingMargin(true);
+                        restWidth -= sp[i].getLeadingMargin(false);
 
-                    // LeadingMarginSpan2 is odd.  The count affects all
-                    // leading margin spans, not just this particular one
-                    if (lms instanceof LeadingMarginSpan2) {
-                        LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
-                        firstWidthLineCount = Math.max(firstWidthLineCount,
-                                lms2.getLeadingMarginLineCount());
+                        // LeadingMarginSpan2 is odd.  The count affects all
+                        // leading margin spans, not just this particular one
+                        if (lms instanceof LeadingMarginSpan2) {
+                            LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+                            firstWidthLineCount = Math.max(firstWidthLineCount,
+                                    lms2.getLeadingMarginLineCount());
+                        }
+                    }
+
+                    chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+
+                    if (chooseHt.length == 0) {
+                        chooseHt = null; // So that out() would not assume it has any contents
+                    } else {
+                        if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+                            chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                        }
+
+                        for (int i = 0; i < chooseHt.length; i++) {
+                            int o = spanned.getSpanStart(chooseHt[i]);
+
+                            if (o < paraStart) {
+                                // starts in this layout, before the
+                                // current paragraph
+
+                                chooseHtv[i] = getLineTop(getLineForOffset(o));
+                            } else {
+                                // starts in this paragraph
+
+                                chooseHtv[i] = v;
+                            }
+                        }
+                    }
+                }
+                // tab stop locations
+                float[] variableTabStops = null;
+                if (spanned != null) {
+                    TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+                            paraEnd, TabStopSpan.class);
+                    if (spans.length > 0) {
+                        float[] stops = new float[spans.length];
+                        for (int i = 0; i < spans.length; i++) {
+                            stops[i] = (float) spans[i].getTabStop();
+                        }
+                        Arrays.sort(stops, 0, stops.length);
+                        variableTabStops = stops;
                     }
                 }
 
-                chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+                final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
+                final char[] chs = measuredPara.getChars();
+                final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
+                final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
 
-                if (chooseHt.length == 0) {
-                    chooseHt = null; // So that out() would not assume it has any contents
-                } else {
-                    if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
-                        chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                constraints.setWidth(restWidth);
+                constraints.setIndent(firstWidth, firstWidthLineCount);
+                constraints.setTabStops(variableTabStops, TAB_INCREMENT);
+
+                if (TRACE_LAYOUT) {
+                    Trace.beginSection("LineBreaker#computeLineBreaks");
+                }
+                LineBreaker.Result res;
+                try {
+                    res = lineBreaker.computeLineBreaks(
+                            measuredPara.getMeasuredText(), constraints, mLineCount);
+                } finally {
+                    if (TRACE_LAYOUT) {
+                        Trace.endSection();
                     }
+                }
+                int breakCount = res.getLineCount();
+                if (lineBreakCapacity < breakCount) {
+                    lineBreakCapacity = breakCount;
+                    breaks = new int[lineBreakCapacity];
+                    lineWidths = new float[lineBreakCapacity];
+                    ascents = new float[lineBreakCapacity];
+                    descents = new float[lineBreakCapacity];
+                    hasTabs = new boolean[lineBreakCapacity];
+                    hyphenEdits = new int[lineBreakCapacity];
+                }
 
-                    for (int i = 0; i < chooseHt.length; i++) {
-                        int o = spanned.getSpanStart(chooseHt[i]);
+                for (int i = 0; i < breakCount; ++i) {
+                    breaks[i] = res.getLineBreakOffset(i);
+                    lineWidths[i] = res.getLineWidth(i);
+                    ascents[i] = res.getLineAscent(i);
+                    descents[i] = res.getLineDescent(i);
+                    hasTabs[i] = res.hasLineTab(i);
+                    hyphenEdits[i] =
+                        packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
+                }
 
-                        if (o < paraStart) {
-                            // starts in this layout, before the
-                            // current paragraph
-
-                            chooseHtv[i] = getLineTop(getLineForOffset(o));
+                final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+                final boolean ellipsisMayBeApplied = ellipsize != null
+                        && (ellipsize == TextUtils.TruncateAt.END
+                            || (mMaximumVisibleLineCount == 1
+                                    && ellipsize != TextUtils.TruncateAt.MARQUEE));
+                if (0 < remainingLineCount && remainingLineCount < breakCount
+                        && ellipsisMayBeApplied) {
+                    // Calculate width
+                    float width = 0;
+                    boolean hasTab = false;  // XXX May need to also have starting hyphen edit
+                    for (int i = remainingLineCount - 1; i < breakCount; i++) {
+                        if (i == breakCount - 1) {
+                            width += lineWidths[i];
                         } else {
-                            // starts in this paragraph
+                            for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+                                width += measuredPara.getCharWidthAt(j);
+                            }
+                        }
+                        hasTab |= hasTabs[i];
+                    }
+                    // Treat the last line and overflowed lines as a single line.
+                    breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+                    lineWidths[remainingLineCount - 1] = width;
+                    hasTabs[remainingLineCount - 1] = hasTab;
 
-                            chooseHtv[i] = v;
+                    breakCount = remainingLineCount;
+                }
+
+                // here is the offset of the starting character of the line we are currently
+                // measuring
+                int here = paraStart;
+
+                int fmTop = defaultTop;
+                int fmBottom = defaultBottom;
+                int fmAscent = defaultAscent;
+                int fmDescent = defaultDescent;
+                int fmCacheIndex = 0;
+                int spanEndCacheIndex = 0;
+                int breakIndex = 0;
+                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+                    // retrieve end of span
+                    spanEnd = spanEndCache[spanEndCacheIndex++];
+
+                    // retrieve cached metrics, order matches above
+                    fm.top = fmCache[fmCacheIndex * 4 + 0];
+                    fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+                    fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+                    fm.descent = fmCache[fmCacheIndex * 4 + 3];
+                    fmCacheIndex++;
+
+                    if (fm.top < fmTop) {
+                        fmTop = fm.top;
+                    }
+                    if (fm.ascent < fmAscent) {
+                        fmAscent = fm.ascent;
+                    }
+                    if (fm.descent > fmDescent) {
+                        fmDescent = fm.descent;
+                    }
+                    if (fm.bottom > fmBottom) {
+                        fmBottom = fm.bottom;
+                    }
+
+                    // skip breaks ending before current span range
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+                        breakIndex++;
+                    }
+
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+                        int endPos = paraStart + breaks[breakIndex];
+
+                        boolean moreChars = (endPos < bufEnd);
+
+                        final int ascent = isFallbackLineSpacing
+                                ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+                                : fmAscent;
+                        final int descent = isFallbackLineSpacing
+                                ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+                                : fmDescent;
+
+                        // The fallback ascent/descent may be larger than top/bottom of the default
+                        // font metrics. Adjust top/bottom with ascent/descent for avoiding
+                        // unexpected clipping.
+                        if (isFallbackLineSpacing) {
+                            if (ascent < fmTop) {
+                                fmTop = ascent;
+                            }
+                            if (descent > fmBottom) {
+                                fmBottom = descent;
+                            }
+                        }
+
+                        v = out(source, here, endPos,
+                                ascent, descent, fmTop, fmBottom,
+                                v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+                                hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
+                                measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
+                                paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
+                                paint, moreChars);
+
+                        if (endPos < spanEnd) {
+                            // preserve metrics for current span
+                            fmTop = Math.min(defaultTop, fm.top);
+                            fmBottom = Math.max(defaultBottom, fm.bottom);
+                            fmAscent = Math.min(defaultAscent, fm.ascent);
+                            fmDescent = Math.max(defaultDescent, fm.descent);
+                        } else {
+                            fmTop = fmBottom = fmAscent = fmDescent = 0;
+                        }
+
+                        here = endPos;
+                        breakIndex++;
+
+                        if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+                            return;
                         }
                     }
                 }
-            }
-            // tab stop locations
-            float[] variableTabStops = null;
-            if (spanned != null) {
-                TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
-                        paraEnd, TabStopSpan.class);
-                if (spans.length > 0) {
-                    float[] stops = new float[spans.length];
-                    for (int i = 0; i < spans.length; i++) {
-                        stops[i] = (float) spans[i].getTabStop();
-                    }
-                    Arrays.sort(stops, 0, stops.length);
-                    variableTabStops = stops;
+
+                if (paraEnd == bufEnd) {
+                    break;
                 }
-            }
-
-            final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
-            final char[] chs = measuredPara.getChars();
-            final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
-            final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
-
-            constraints.setWidth(restWidth);
-            constraints.setIndent(firstWidth, firstWidthLineCount);
-            constraints.setTabStops(variableTabStops, TAB_INCREMENT);
-
-            LineBreaker.Result res = lineBreaker.computeLineBreaks(
-                    measuredPara.getMeasuredText(), constraints, mLineCount);
-            int breakCount = res.getLineCount();
-            if (lineBreakCapacity < breakCount) {
-                lineBreakCapacity = breakCount;
-                breaks = new int[lineBreakCapacity];
-                lineWidths = new float[lineBreakCapacity];
-                ascents = new float[lineBreakCapacity];
-                descents = new float[lineBreakCapacity];
-                hasTabs = new boolean[lineBreakCapacity];
-                hyphenEdits = new int[lineBreakCapacity];
-            }
-
-            for (int i = 0; i < breakCount; ++i) {
-                breaks[i] = res.getLineBreakOffset(i);
-                lineWidths[i] = res.getLineWidth(i);
-                ascents[i] = res.getLineAscent(i);
-                descents[i] = res.getLineDescent(i);
-                hasTabs[i] = res.hasLineTab(i);
-                hyphenEdits[i] =
-                    packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
-            }
-
-            final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
-            final boolean ellipsisMayBeApplied = ellipsize != null
-                    && (ellipsize == TextUtils.TruncateAt.END
-                        || (mMaximumVisibleLineCount == 1
-                                && ellipsize != TextUtils.TruncateAt.MARQUEE));
-            if (0 < remainingLineCount && remainingLineCount < breakCount
-                    && ellipsisMayBeApplied) {
-                // Calculate width
-                float width = 0;
-                boolean hasTab = false;  // XXX May need to also have starting hyphen edit
-                for (int i = remainingLineCount - 1; i < breakCount; i++) {
-                    if (i == breakCount - 1) {
-                        width += lineWidths[i];
-                    } else {
-                        for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
-                            width += measuredPara.getCharWidthAt(j);
-                        }
-                    }
-                    hasTab |= hasTabs[i];
+            } finally {
+                if (TRACE_LAYOUT) {
+                    Trace.endSection();
                 }
-                // Treat the last line and overflowed lines as a single line.
-                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
-                lineWidths[remainingLineCount - 1] = width;
-                hasTabs[remainingLineCount - 1] = hasTab;
-
-                breakCount = remainingLineCount;
-            }
-
-            // here is the offset of the starting character of the line we are currently
-            // measuring
-            int here = paraStart;
-
-            int fmTop = defaultTop;
-            int fmBottom = defaultBottom;
-            int fmAscent = defaultAscent;
-            int fmDescent = defaultDescent;
-            int fmCacheIndex = 0;
-            int spanEndCacheIndex = 0;
-            int breakIndex = 0;
-            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
-                // retrieve end of span
-                spanEnd = spanEndCache[spanEndCacheIndex++];
-
-                // retrieve cached metrics, order matches above
-                fm.top = fmCache[fmCacheIndex * 4 + 0];
-                fm.bottom = fmCache[fmCacheIndex * 4 + 1];
-                fm.ascent = fmCache[fmCacheIndex * 4 + 2];
-                fm.descent = fmCache[fmCacheIndex * 4 + 3];
-                fmCacheIndex++;
-
-                if (fm.top < fmTop) {
-                    fmTop = fm.top;
-                }
-                if (fm.ascent < fmAscent) {
-                    fmAscent = fm.ascent;
-                }
-                if (fm.descent > fmDescent) {
-                    fmDescent = fm.descent;
-                }
-                if (fm.bottom > fmBottom) {
-                    fmBottom = fm.bottom;
-                }
-
-                // skip breaks ending before current span range
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
-                    breakIndex++;
-                }
-
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
-                    int endPos = paraStart + breaks[breakIndex];
-
-                    boolean moreChars = (endPos < bufEnd);
-
-                    final int ascent = isFallbackLineSpacing
-                            ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
-                            : fmAscent;
-                    final int descent = isFallbackLineSpacing
-                            ? Math.max(fmDescent, Math.round(descents[breakIndex]))
-                            : fmDescent;
-
-                    // The fallback ascent/descent may be larger than top/bottom of the default font
-                    // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected
-                    // clipping.
-                    if (isFallbackLineSpacing) {
-                        if (ascent < fmTop) {
-                            fmTop = ascent;
-                        }
-                        if (descent > fmBottom) {
-                            fmBottom = descent;
-                        }
-                    }
-
-                    v = out(source, here, endPos,
-                            ascent, descent, fmTop, fmBottom,
-                            v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
-                            hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
-                            measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
-                            paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
-                            paint, moreChars);
-
-                    if (endPos < spanEnd) {
-                        // preserve metrics for current span
-                        fmTop = Math.min(defaultTop, fm.top);
-                        fmBottom = Math.max(defaultBottom, fm.bottom);
-                        fmAscent = Math.min(defaultAscent, fm.ascent);
-                        fmDescent = Math.max(defaultDescent, fm.descent);
-                    } else {
-                        fmTop = fmBottom = fmAscent = fmDescent = 0;
-                    }
-
-                    here = endPos;
-                    breakIndex++;
-
-                    if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
-                        return;
-                    }
-                }
-            }
-
-            if (paraEnd == bufEnd) {
-                break;
             }
         }
 
@@ -1179,9 +1227,18 @@
                     (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
                             ellipsize == TextUtils.TruncateAt.END);
             if (doEllipsis) {
-                calculateEllipsis(start, end, measured, widthStart,
-                        ellipsisWidth, ellipsize, j,
-                        textWidth, paint, forceEllipsis);
+                if (TRACE_LAYOUT) {
+                    Trace.beginSection("StaticLayout#calculateEllipsis");
+                }
+                try {
+                    calculateEllipsis(start, end, measured, widthStart,
+                            ellipsisWidth, ellipsize, j,
+                            textWidth, paint, forceEllipsis);
+                } finally {
+                    if (TRACE_LAYOUT) {
+                        Trace.endSection();
+                    }
+                }
             } else {
                 mLines[mColumns * j + ELLIPSIS_START] = 0;
                 mLines[mColumns * j + ELLIPSIS_COUNT] = 0;
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index bde9c77..a439478 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -28,6 +28,7 @@
 import android.graphics.text.PositionedGlyphs;
 import android.graphics.text.TextRunShaper;
 import android.os.Build;
+import android.os.Trace;
 import android.text.Layout.Directions;
 import android.text.Layout.TabStops;
 import android.text.style.CharacterStyle;
@@ -56,6 +57,8 @@
 public class TextLine {
     private static final boolean DEBUG = false;
 
+    private static final boolean TRACE_TEXTLINE = Build.isDebuggable();
+
     private static final char TAB_CHAR = '\t';
 
     private TextPaint mPaint;
@@ -430,28 +433,37 @@
      * @param bottom the bottom of the line
      */
     void draw(Canvas c, float x, int top, int y, int bottom) {
-        float h = 0;
-        final int runCount = mDirections.getRunCount();
-        for (int runIndex = 0; runIndex < runCount; runIndex++) {
-            final int runStart = mDirections.getRunStart(runIndex);
-            if (runStart > mLen) break;
-            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
-            final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+        if (TRACE_TEXTLINE) {
+            Trace.beginSection("TextLine#draw");
+        }
+        try {
+            float h = 0;
+            final int runCount = mDirections.getRunCount();
+            for (int runIndex = 0; runIndex < runCount; runIndex++) {
+                final int runStart = mDirections.getRunStart(runIndex);
+                if (runStart > mLen) break;
+                final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+                final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
-            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+                final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
-            int segStart = runStart;
-            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
-                if (j == runLimit || charAt(j) == TAB_CHAR) {
-                    h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
-                            runIndex != (runCount - 1) || j != mLen, runFlag);
+                int segStart = runStart;
+                for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+                    if (j == runLimit || charAt(j) == TAB_CHAR) {
+                        h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
+                                runIndex != (runCount - 1) || j != mLen, runFlag);
 
-                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
-                        h = mDir * nextTab(h * mDir);
+                        if (j != runLimit) {  // charAt(j) == TAB_CHAR
+                            h = mDir * nextTab(h * mDir);
+                        }
+                        segStart = j + 1;
                     }
-                    segStart = j + 1;
                 }
             }
+        } finally {
+            if (TRACE_TEXTLINE) {
+                Trace.endSection();
+            }
         }
     }
 
@@ -564,63 +576,76 @@
      */
     public float measure(@IntRange(from = 0) int offset, boolean trailing,
             @NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
-        if (offset > mLen) {
-            throw new IndexOutOfBoundsException(
-                    "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+        if (TRACE_TEXTLINE) {
+            Trace.beginSection("TextLine#measure");
         }
-        if (lineInfo != null) {
-            lineInfo.setClusterCount(0);
-        }
-        final int target = trailing ? offset - 1 : offset;
-        if (target < 0) {
-            return 0;
-        }
+        try {
+            if (offset > mLen) {
+                throw new IndexOutOfBoundsException(
+                        "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+            }
+            if (lineInfo != null) {
+                lineInfo.setClusterCount(0);
+            }
+            final int target = trailing ? offset - 1 : offset;
+            if (target < 0) {
+                return 0;
+            }
 
-        float h = 0;
-        final int runCount = mDirections.getRunCount();
-        for (int runIndex = 0; runIndex < runCount; runIndex++) {
-            final int runStart = mDirections.getRunStart(runIndex);
-            if (runStart > mLen) break;
-            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
-            final boolean runIsRtl = mDirections.isRunRtl(runIndex);
-            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+            float h = 0;
+            final int runCount = mDirections.getRunCount();
+            for (int runIndex = 0; runIndex < runCount; runIndex++) {
+                final int runStart = mDirections.getRunStart(runIndex);
+                if (runStart > mLen) break;
+                final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+                final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+                final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
-            int segStart = runStart;
-            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
-                if (j == runLimit || charAt(j) == TAB_CHAR) {
-                    final boolean targetIsInThisSegment = target >= segStart && target < j;
-                    final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+                int segStart = runStart;
+                for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+                    if (j == runLimit || charAt(j) == TAB_CHAR) {
+                        final boolean targetIsInThisSegment = target >= segStart && target < j;
+                        final boolean sameDirection =
+                                (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
 
-                    if (targetIsInThisSegment && sameDirection) {
-                        return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
-                                0, h, lineInfo, runFlag);
-                    }
-
-                    final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
-                            null, 0, h, lineInfo, runFlag);
-                    h += sameDirection ? segmentWidth : -segmentWidth;
-
-                    if (targetIsInThisSegment) {
-                        return h + measureRun(segStart, offset, j, runIsRtl, null, null,  null, 0,
-                                h, lineInfo, runFlag);
-                    }
-
-                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
-                        if (offset == j) {
-                            return h;
+                        if (targetIsInThisSegment && sameDirection) {
+                            return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds,
+                                    null,
+                                    0, h, lineInfo, runFlag);
                         }
-                        h = mDir * nextTab(h * mDir);
-                        if (target == j) {
-                            return h;
-                        }
-                    }
 
-                    segStart = j + 1;
+                        final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi,
+                                drawBounds,
+                                null, 0, h, lineInfo, runFlag);
+                        h += sameDirection ? segmentWidth : -segmentWidth;
+
+                        if (targetIsInThisSegment) {
+                            return h + measureRun(segStart, offset, j, runIsRtl, null, null, null,
+                                    0,
+                                    h, lineInfo, runFlag);
+                        }
+
+                        if (j != runLimit) {  // charAt(j) == TAB_CHAR
+                            if (offset == j) {
+                                return h;
+                            }
+                            h = mDir * nextTab(h * mDir);
+                            if (target == j) {
+                                return h;
+                            }
+                        }
+
+                        segStart = j + 1;
+                    }
                 }
             }
-        }
 
-        return h;
+            return h;
+        } finally {
+            if (TRACE_TEXTLINE) {
+                Trace.endSection();
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a5ff48f..0ac8936 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2372,6 +2372,12 @@
     protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
 
     /**
+     * This indicates that the frame rate category was chosen for an unknown reason.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_UNKNOWN = 0x0000_0000;
+
+    /**
      * This indicates that the frame rate category was chosen because it was a small area update.
      * @hide
      */
@@ -2402,9 +2408,24 @@
      */
     public static final int FRAME_RATE_CATEGORY_REASON_INVALID = 0x0500_0000;
 
+    /**
+     * This indicates that the frame rate category was chosen because the view has a velocity
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_VELOCITY = 0x0600_0000;
+
+    /**
+     * This indicates that the frame rate category was chosen because it is idle.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_IDLE = 0x0700_0000;
+
     private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
 
-    private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+    /**
+     * @hide
+     */
+    protected static boolean sToolkitSetFrameRateReadOnlyFlagValue;
     private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
     // Used to set frame rate compatibility.
     @Surface.FrameRateCompatibility int mFrameRateCompatibility =
@@ -33778,6 +33799,10 @@
                     return;
                 }
             }
+            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+                float sizePercentage = getSizePercentage();
+                viewRootImpl.recordViewPercentage(sizePercentage);
+            }
             int frameRateCategory;
             if (Float.isNaN(mPreferredFrameRate)) {
                 frameRateCategory = calculateFrameRateCategory(width, height);
@@ -33806,11 +33831,8 @@
             }
 
             int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
-            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
-                int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
-                viewRootImpl.recordCategory(category, reason, this);
-            }
-            viewRootImpl.votePreferredFrameRateCategory(category);
+            int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+            viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
             mLastFrameRateCategory = frameRateCategory;
         }
     }
@@ -33906,9 +33928,9 @@
     }
 
     /**
-     * This function is mainly used for migrating infrequent layer lagic
+     * This function is mainly used for migrating infrequent layer logic
      * from SurfaceFlinger to Toolkit.
-     * The infrequent layter logic includes:
+     * The infrequent layer logic includes:
      * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
      * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
      * - otherwise, use the previous category value.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6f178bb..8c3cf5f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -32,11 +32,14 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_IDLE;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_VELOCITY;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -847,6 +850,8 @@
     private boolean mInsetsAnimationRunning;
 
     private long mPreviousFrameDrawnTime = -1;
+    // The largest view size percentage to the display size. Used on trace to collect metric.
+    private float mLargestChildPercentage = 0.0f;
     // The reason the category was changed.
     private int mFrameRateCategoryChangeReason = 0;
     private String mFrameRateCategoryView;
@@ -4854,6 +4859,10 @@
         long fps = NANOS_PER_SEC / timeDiff;
         Trace.setCounter(mFpsTraceName, fps);
         mPreviousFrameDrawnTime = expectedDrawnTime;
+
+        long percentage = (long) (mLargestChildPercentage * 100);
+        Trace.setCounter(mLargestViewTraceName, percentage);
+        mLargestChildPercentage = 0.0f;
     }
 
     private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -6540,6 +6549,8 @@
                 case MSG_CHECK_INVALIDATION_IDLE:
                     if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
                         mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+                        mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_IDLE;
+                        mFrameRateCategoryView = null;
                         setPreferredFrameRateCategory(mPreferredFrameRateCategory);
                         mHasIdledMessage = false;
                     } else {
@@ -12367,24 +12378,37 @@
                 || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
                         && mPreferredFrameRate > 0)) {
             frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+            if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) {
+                // We've received a velocity, so we'll let the velocity control the
+                // frame rate unless we receive additional motion events.
+                mIsTouchBoosting = false;
+                mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY;
+                mFrameRateCategoryView = null;
+            } else {
+                mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+            }
         }
 
         try {
             if (mLastPreferredFrameRateCategory != frameRateCategory) {
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                    String reason = "none";
-                    switch (mFrameRateCategoryChangeReason) {
-                        case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> reason = "intermittent";
-                        case FRAME_RATE_CATEGORY_REASON_SMALL -> reason = "small";
-                        case FRAME_RATE_CATEGORY_REASON_LARGE -> reason = "large";
-                        case FRAME_RATE_CATEGORY_REASON_REQUESTED -> reason = "requested";
-                        case FRAME_RATE_CATEGORY_REASON_INVALID -> reason = "invalid frame rate";
-                    }
-                    String sourceView = mFrameRateCategoryView == null ? "No View Given"
+                    String reason = reasonToString(mFrameRateCategoryChangeReason);
+                    String sourceView = mFrameRateCategoryView == null ? "-"
                             : mFrameRateCategoryView;
+                    if (preferredFrameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
+                        reason = "touch boost";
+                        sourceView = "-";
+                    } else if (categoryFromConflictedFrameRates == frameRateCategory
+                            && frameRateCategory != preferredFrameRateCategory
+                            && mIsFrameRateConflicted
+                    ) {
+                        reason = "conflict";
+                        sourceView = "-";
+                    }
+                    String category = categoryToString(frameRateCategory);
                     Trace.traceBegin(
                             Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
-                                    + frameRateCategory + ", reason " + reason + ", "
+                                    + category + ", reason " + reason + ", "
                                     + sourceView);
                 }
                 mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
@@ -12398,6 +12422,35 @@
         }
     }
 
+    private static String categoryToString(int frameRateCategory) {
+        String category;
+        switch (frameRateCategory) {
+            case FRAME_RATE_CATEGORY_NO_PREFERENCE -> category = "no preference";
+            case FRAME_RATE_CATEGORY_LOW -> category = "low";
+            case FRAME_RATE_CATEGORY_NORMAL -> category = "normal";
+            case FRAME_RATE_CATEGORY_HIGH_HINT -> category = "high hint";
+            case FRAME_RATE_CATEGORY_HIGH -> category = "high";
+            default -> category = "default";
+        }
+        return category;
+    }
+
+    private static String reasonToString(int reason) {
+        String str;
+        switch (reason) {
+            case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> str = "intermittent";
+            case FRAME_RATE_CATEGORY_REASON_SMALL -> str = "small";
+            case FRAME_RATE_CATEGORY_REASON_LARGE -> str = "large";
+            case FRAME_RATE_CATEGORY_REASON_REQUESTED -> str = "requested";
+            case FRAME_RATE_CATEGORY_REASON_INVALID -> str = "invalid frame rate";
+            case FRAME_RATE_CATEGORY_REASON_VELOCITY -> str = "velocity";
+            case FRAME_RATE_CATEGORY_REASON_IDLE -> str = "idle";
+            case FRAME_RATE_CATEGORY_REASON_UNKNOWN -> str = "unknown";
+            default -> str = String.valueOf(reason);
+        }
+        return str;
+    }
+
     private void setPreferredFrameRate(float preferredFrameRate) {
         if (!shouldSetFrameRate() || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
                 && preferredFrameRate > 0)) {
@@ -12417,17 +12470,6 @@
                     mFrameRateCompatibility).applyAsyncUnsafe();
                 mLastPreferredFrameRate = preferredFrameRate;
             }
-            if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && mIsTouchBoosting) {
-                // We've received a velocity, so we'll let the velocity control the
-                // frame rate unless we receive additional motion events.
-                mIsTouchBoosting = false;
-                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                    Trace.instant(
-                            Trace.TRACE_TAG_VIEW,
-                            "ViewRootImpl#setFrameRate velocity used, no touch boost on next frame"
-                    );
-                }
-            }
         } catch (Exception e) {
             Log.e(mTag, "Unable to set frame rate", e);
         } finally {
@@ -12468,7 +12510,7 @@
      * @param frameRateCategory the preferred frame rate category of a View
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
-    public void votePreferredFrameRateCategory(int frameRateCategory) {
+    public void votePreferredFrameRateCategory(int frameRateCategory, int reason, View view) {
         if (frameRateCategory == FRAME_RATE_CATEGORY_HIGH) {
             mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
         } else if (frameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
@@ -12479,6 +12521,7 @@
             mFrameRateCategoryLowCount = FRAME_RATE_CATEGORY_COUNT;
         }
 
+        int oldCategory = mPreferredFrameRateCategory;
         if (mFrameRateCategoryHighCount > 0) {
             mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
         } else if (mFrameRateCategoryHighHintCount > 0) {
@@ -12490,6 +12533,13 @@
         }
         mHasInvalidation = true;
         checkIdleness();
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)
+                && mPreferredFrameRateCategory != oldCategory
+                && mPreferredFrameRateCategory == frameRateCategory
+        ) {
+            mFrameRateCategoryChangeReason = reason;
+            mFrameRateCategoryView = view.getClass().getSimpleName();
+        }
     }
 
     /**
@@ -12617,12 +12667,10 @@
         mWindowlessBackKeyCallback = callback;
     }
 
-    void recordCategory(int category, int reason, View view) {
+    void recordViewPercentage(float percentage) {
         if (!Trace.isEnabled()) return;
-        if (category > mPreferredFrameRateCategory) {
-            mFrameRateCategoryChangeReason = reason;
-            mFrameRateCategoryView = view.getClass().getSimpleName();
-        }
+        // Record the largest view of percentage to the display size.
+        mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
     }
 
     /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 64846d0..1e3e81f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -93,8 +93,12 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.app.ActivityTaskManager;
+import android.app.ActivityThread;
 import android.app.KeyguardManager;
 import android.app.Presentation;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
 import android.content.ComponentName;
@@ -1416,23 +1420,71 @@
     public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
 
     /**
-     * Whether the device supports the WindowManager Extensions.
-     * OEMs can enable this by having their device config to inherit window_extensions.mk, such as:
+     * Whether the WindowManager Extensions - Activity Embedding feature should be guarded by
+     * the app's target SDK on Android 15.
+     *
+     * WindowManager Extensions are only required for foldable and large screen before Android 15,
+     * so we want to guard the Activity Embedding feature since it can have app compat impact on
+     * devices with a compact size display.
+     *
+     * <p>If {@code true}, the feature is only enabled if the app's target SDK is Android 15 or
+     * above.
+     *
+     * <p>If {@code false}, the feature is enabled for all apps.
+     *
+     * <p>The default value is {@code true}. OEMs can set to {@code false} by having their device
+     * config to inherit window_extensions.mk. This is also required for large screen devices.
      * <pre>
      * $(call inherit-product, $(SRC_TARGET_DIR)/product/window_extensions.mk)
      * </pre>
+     *
      * @hide
      */
-    boolean WINDOW_EXTENSIONS_ENABLED =
+    boolean ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15 = SystemProperties.getBoolean(
+            "persist.wm.extensions.activity_embedding_guard_with_android_15", true);
+
+    /**
+     * For devices with {@link #ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15} as {@code true},
+     * the Activity Embedding feature is enabled if the app's target SDK is Android 15+.
+     *
+     * @see #ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    long ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15 = 306666082L;
+
+    /**
+     * Whether the device contains the WindowManager Extensions shared library.
+     * This is enabled for all devices through window_extensions_base.mk, but can be dropped if the
+     * device doesn't support multi window.
+     *
+     * <p>Note: Large screen devices must also inherit window_extensions.mk to enable the Activity
+     * Embedding feature by default for all apps.
+     *
+     * @see #ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15
+     * @hide
+     */
+    boolean HAS_WINDOW_EXTENSIONS_ON_DEVICE =
             SystemProperties.getBoolean("persist.wm.extensions.enabled", false);
 
     /**
-     * @see #WINDOW_EXTENSIONS_ENABLED
+     * Whether the WindowManager Extensions are enabled.
+     * If {@code false}, the WM Jetpack will report most of its features as disabled.
+     * @see #HAS_WINDOW_EXTENSIONS_ON_DEVICE
      * @hide
      */
     @TestApi
     static boolean hasWindowExtensionsEnabled() {
-        return WINDOW_EXTENSIONS_ENABLED;
+        return HAS_WINDOW_EXTENSIONS_ON_DEVICE
+                && ActivityTaskManager.supportsMultiWindow(ActivityThread.currentApplication())
+                // Since enableWmExtensionsForAllFlag, HAS_WINDOW_EXTENSIONS_ON_DEVICE is now true
+                // on all devices by default as a build file property.
+                // Until finishing flag ramp up, only return true when
+                // ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15 is false, which is set per device by
+                // OEMs.
+                && (Flags.enableWmExtensionsForAllFlag()
+                || !ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15);
     }
 
     /**
@@ -1571,8 +1623,9 @@
     /**
      * Value applicable for the {@link #PROPERTY_COMPAT_ALLOW_SMALL_COVER_SCREEN} property to
      * provide a signal to the system that an application or its specific activities explicitly
-     * opt into being displayed on small foldable device cover screens that measure at least 1.5
-     * inches for the shorter dimension and at least 2.4 inches for the longer dimension.
+     * opt into being displayed on small cover screens on flippable style foldable devices that
+     * measure at least 1.5 inches up to 2.2 inches for the shorter dimension and at least 2.4
+     * inches up to 3.4 inches for the longer dimension
      */
     @CompatSmallScreenPolicy
     @FlaggedApi(Flags.FLAG_COVER_DISPLAY_OPT_IN)
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index e215950..614df7c 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -142,7 +142,7 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
     void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
 
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.STATUS_BAR_SERVICE,android.Manifest.permission.MANAGE_ACCESSIBILITY})")
     oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 139ebc3..d40eeda 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2112,6 +2112,17 @@
             }
         }
 
+        boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
+                && canvas.isHighContrastTextEnabled();
+
+        // If high contrast text is drawing background rectangles behind the text, those cover up
+        // the cursor and correction highlighter etc. So just draw the text first, then draw the
+        // others on top of the text. If high contrast text isn't enabled: draw text last, as usual.
+        if (shouldDrawHighlightsOnTop) {
+            drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+                    selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+        }
+
         if (mCorrectionHighlighter != null) {
             mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
         }
@@ -2136,9 +2147,19 @@
             mInsertModeController.onDraw(canvas);
         }
 
+        if (!shouldDrawHighlightsOnTop) {
+            drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+                    selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+        }
+    }
+
+    private void drawLayout(Canvas canvas, Layout layout, List<Path> highlightPaths,
+            List<Paint> highlightPaints, Path selectionHighlight, Paint selectionHighlightPaint,
+            int cursorOffsetVertical, boolean shouldDrawHighlightsOnTop) {
         if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
             drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints,
-                    selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
+                    selectionHighlight, selectionHighlightPaint, cursorOffsetVertical,
+                    shouldDrawHighlightsOnTop);
         } else {
             layout.draw(canvas, highlightPaths, highlightPaints, selectionHighlight,
                     selectionHighlightPaint, cursorOffsetVertical);
@@ -2147,14 +2168,13 @@
 
     private void drawHardwareAccelerated(Canvas canvas, Layout layout,
             List<Path> highlightPaths, List<Paint> highlightPaints,
-            Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) {
+            Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical,
+            boolean shouldDrawHighlightsOnTop) {
         final long lineRange = layout.getLineRangeForDraw(canvas);
         int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
         int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
         if (lastLine < 0) return;
 
-        boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
-                && canvas.isHighContrastTextEnabled();
 
         if (!shouldDrawHighlightsOnTop) {
             layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight,
diff --git a/core/java/android/window/InputTransferToken.java b/core/java/android/window/InputTransferToken.java
index e572853..c62eee4 100644
--- a/core/java/android/window/InputTransferToken.java
+++ b/core/java/android/window/InputTransferToken.java
@@ -18,7 +18,6 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Parcel;
@@ -30,6 +29,8 @@
 
 import com.android.window.flags.Flags;
 
+import libcore.util.NativeAllocationRegistry;
+
 import java.util.Objects;
 
 /**
@@ -51,28 +52,51 @@
  */
 @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
 public final class InputTransferToken implements Parcelable {
+    private static native long nativeCreate();
+    private static native long nativeCreate(IBinder token);
+    private static native void nativeWriteToParcel(long nativeObject, Parcel out);
+    private static native long nativeReadFromParcel(Parcel in);
+    private static native IBinder nativeGetBinderToken(long nativeObject);
+    private static native long nativeGetNativeInputTransferTokenFinalizer();
+    private static native boolean nativeEquals(long nativeObject1, long nativeObject2);
+
+    private static final NativeAllocationRegistry sRegistry =
+            NativeAllocationRegistry.createMalloced(InputTransferToken.class.getClassLoader(),
+                    nativeGetNativeInputTransferTokenFinalizer());
+
     /**
      * @hide
      */
-    @NonNull
-    public final IBinder mToken;
+    public final long mNativeObject;
+
+    private InputTransferToken(long nativeObject) {
+        mNativeObject = nativeObject;
+        sRegistry.registerNativeAllocation(this, nativeObject);
+    }
 
     /**
      * @hide
      */
     public InputTransferToken(@NonNull IBinder token) {
-        mToken = token;
+        this(nativeCreate(token));
     }
 
     /**
      * @hide
      */
     public InputTransferToken() {
-        mToken = new Binder();
+        this(nativeCreate());
+    }
+
+    /**
+     * @hide
+     */
+    public IBinder getToken() {
+        return nativeGetBinderToken(mNativeObject);
     }
 
     private InputTransferToken(Parcel in) {
-        mToken = in.readStrongBinder();
+        this(nativeReadFromParcel(in));
     }
 
     /**
@@ -88,7 +112,7 @@
      */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeStrongBinder(mToken);
+        nativeWriteToParcel(mNativeObject, dest);
     }
 
     public static final @NonNull Creator<InputTransferToken> CREATOR = new Creator<>() {
@@ -101,13 +125,12 @@
         }
     };
 
-
     /**
      * @hide
      */
     @Override
     public int hashCode() {
-        return Objects.hash(mToken);
+        return Objects.hash(getToken());
     }
 
     /**
@@ -118,7 +141,8 @@
         if (this == obj) return true;
         if (obj == null || getClass() != obj.getClass()) return false;
         InputTransferToken other = (InputTransferToken) obj;
-        return other.mToken == mToken;
+        if (other.mNativeObject == mNativeObject) return true;
+        return nativeEquals(mNativeObject, other.mNativeObject);
     }
 
 }
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 6999e5b..50f5191 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -59,28 +59,35 @@
      */
     public boolean playRevealAnimation;
 
-    /** The mode is no need to defer removing the starting window for IME */
-    public static final int DEFER_MODE_NONE = 0;
+    /** The mode is default defer removing the snapshot starting window. */
+    public static final int DEFER_MODE_DEFAULT = 0;
 
-    /** The mode to defer removing the starting window until IME has drawn */
+    /** The mode to defer removing the snapshot starting window until IME has drawn. */
     public static final int DEFER_MODE_NORMAL = 1;
 
-    /** The mode to defer the starting window removal until IME drawn and finished the rotation */
+    /**
+     * The mode to defer the snapshot starting window removal until IME drawn and finished the
+     * rotation.
+     */
     public static final int DEFER_MODE_ROTATION = 2;
 
+    /** The mode is no need to defer removing the snapshot starting window. */
+    public static final int DEFER_MODE_NONE = 3;
+
     @IntDef(prefix = { "DEFER_MODE_" }, value = {
-            DEFER_MODE_NONE,
+            DEFER_MODE_DEFAULT,
             DEFER_MODE_NORMAL,
             DEFER_MODE_ROTATION,
+            DEFER_MODE_NONE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DeferMode {}
 
     /**
-     * Whether need to defer removing the starting window for IME.
+     * Whether need to defer removing the snapshot starting window.
      * @hide
      */
-    public @DeferMode int deferRemoveForImeMode;
+    public @DeferMode int deferRemoveMode;
 
     /**
      * The rounded corner radius
@@ -116,7 +123,7 @@
         windowAnimationLeash = source.readTypedObject(SurfaceControl.CREATOR);
         mainFrame = source.readTypedObject(Rect.CREATOR);
         playRevealAnimation = source.readBoolean();
-        deferRemoveForImeMode = source.readInt();
+        deferRemoveMode = source.readInt();
         roundedCornerRadius = source.readFloat();
         windowlessSurface = source.readBoolean();
         removeImmediately = source.readBoolean();
@@ -128,7 +135,7 @@
         dest.writeTypedObject(windowAnimationLeash, flags);
         dest.writeTypedObject(mainFrame, flags);
         dest.writeBoolean(playRevealAnimation);
-        dest.writeInt(deferRemoveForImeMode);
+        dest.writeInt(deferRemoveMode);
         dest.writeFloat(roundedCornerRadius);
         dest.writeBoolean(windowlessSurface);
         dest.writeBoolean(removeImmediately);
@@ -140,7 +147,7 @@
                 + " frame=" + mainFrame
                 + " playRevealAnimation=" + playRevealAnimation
                 + " roundedCornerRadius=" + roundedCornerRadius
-                + " deferRemoveForImeMode=" + deferRemoveForImeMode
+                + " deferRemoveMode=" + deferRemoveMode
                 + " windowlessSurface=" + windowlessSurface
                 + " removeImmediately=" + removeImmediately + "}";
     }
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 7b8cdff..7e77f15 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -24,6 +24,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.SurfaceControl;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -111,7 +112,8 @@
     /**
      * Creates a decor surface in the parent Task of the TaskFragment. The created decor surface
      * will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED}
-     * event callback.
+     * event callback. The decor surface can be used to draw the divider between TaskFragments or
+     * other decorations.
      */
     public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14;
 
@@ -135,6 +137,15 @@
      */
     public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;
 
+    /**
+     * Sets whether the decor surface will be boosted. When not boosted, the decor surface is placed
+     * below any TaskFragments in untrusted mode or any activities with uid different from the
+     * TaskFragmentOrganizer uid and just above its owner TaskFragment; when boosted, the decor
+     * surface is placed above all the non-boosted windows in the Task, the content of these
+     * non-boosted windows will be hidden and inputs are disabled.
+     */
+    public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -155,6 +166,7 @@
             OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
             OP_TYPE_SET_DIM_ON_TASK,
             OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
+            OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
@@ -186,12 +198,18 @@
 
     private final boolean mMoveToBottomIfClearWhenLaunch;
 
+    private final boolean mBooleanValue;
+
+    @Nullable
+    private final SurfaceControl.Transaction mSurfaceTransaction;
+
     private TaskFragmentOperation(@OperationType int opType,
             @Nullable TaskFragmentCreationParams taskFragmentCreationParams,
             @Nullable IBinder activityToken, @Nullable Intent activityIntent,
             @Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
             @Nullable TaskFragmentAnimationParams animationParams,
-            boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) {
+            boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch,
+            boolean booleanValue, @Nullable SurfaceControl.Transaction surfaceTransaction) {
         mOpType = opType;
         mTaskFragmentCreationParams = taskFragmentCreationParams;
         mActivityToken = activityToken;
@@ -202,6 +220,8 @@
         mIsolatedNav = isolatedNav;
         mDimOnTask = dimOnTask;
         mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
+        mBooleanValue = booleanValue;
+        mSurfaceTransaction = surfaceTransaction;
     }
 
     private TaskFragmentOperation(Parcel in) {
@@ -215,6 +235,8 @@
         mIsolatedNav = in.readBoolean();
         mDimOnTask = in.readBoolean();
         mMoveToBottomIfClearWhenLaunch = in.readBoolean();
+        mBooleanValue = in.readBoolean();
+        mSurfaceTransaction = in.readTypedObject(SurfaceControl.Transaction.CREATOR);
     }
 
     @Override
@@ -229,6 +251,8 @@
         dest.writeBoolean(mIsolatedNav);
         dest.writeBoolean(mDimOnTask);
         dest.writeBoolean(mMoveToBottomIfClearWhenLaunch);
+        dest.writeBoolean(mBooleanValue);
+        dest.writeTypedObject(mSurfaceTransaction, flags);
     }
 
     @NonNull
@@ -324,6 +348,22 @@
         return mMoveToBottomIfClearWhenLaunch;
     }
 
+    /** Returns the boolean value for this operation. */
+    public boolean getBooleanValue() {
+        return mBooleanValue;
+    }
+
+    /**
+     * Returns {@link SurfaceControl.Transaction} associated with this operation. Currently, this is
+     * only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to specify a
+     * {@link SurfaceControl.Transaction} that should be applied together with the transaction to
+     * change the decor surface layers.
+     */
+    @Nullable
+    public SurfaceControl.Transaction getSurfaceTransaction() {
+        return mSurfaceTransaction;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
@@ -349,6 +389,10 @@
         sb.append(", isolatedNav=").append(mIsolatedNav);
         sb.append(", dimOnTask=").append(mDimOnTask);
         sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch);
+        sb.append(", booleanValue=").append(mBooleanValue);
+        if (mSurfaceTransaction != null) {
+            sb.append(", surfaceTransaction=").append(mSurfaceTransaction);
+        }
 
         sb.append('}');
         return sb.toString();
@@ -358,7 +402,7 @@
     public int hashCode() {
         return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
                 mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask,
-                mMoveToBottomIfClearWhenLaunch);
+                mMoveToBottomIfClearWhenLaunch, mBooleanValue, mSurfaceTransaction);
     }
 
     @Override
@@ -376,7 +420,9 @@
                 && Objects.equals(mAnimationParams, other.mAnimationParams)
                 && mIsolatedNav == other.mIsolatedNav
                 && mDimOnTask == other.mDimOnTask
-                && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch;
+                && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch
+                && mBooleanValue == other.mBooleanValue
+                && Objects.equals(mSurfaceTransaction, other.mSurfaceTransaction);
     }
 
     @Override
@@ -414,6 +460,11 @@
 
         private boolean mMoveToBottomIfClearWhenLaunch;
 
+        private boolean mBooleanValue;
+
+        @Nullable
+        private SurfaceControl.Transaction mSurfaceTransaction;
+
         /**
          * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
          */
@@ -505,13 +556,37 @@
         }
 
         /**
+         * Sets the boolean value for this operation.
+         * TODO(b/327338038) migrate other boolean values to use shared mBooleanValue
+         */
+        @NonNull
+        public Builder setBooleanValue(boolean booleanValue) {
+            mBooleanValue = booleanValue;
+            return this;
+        }
+
+        /**
+         * Sets {@link SurfaceControl.Transaction} associated with this operation. Currently, this
+         * is only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to
+         * specify a {@link SurfaceControl.Transaction} that should be applied together with the
+         * transaction to change the decor surface layers.
+         */
+        @NonNull
+        public Builder setSurfaceTransaction(
+                @Nullable SurfaceControl.Transaction surfaceTransaction) {
+            mSurfaceTransaction = surfaceTransaction;
+            return this;
+        }
+
+        /**
          * Constructs the {@link TaskFragmentOperation}.
          */
         @NonNull
         public TaskFragmentOperation build() {
             return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
                     mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
-                    mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch);
+                    mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch, mBooleanValue,
+                    mSurfaceTransaction);
         }
     }
 }
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 63a2474..ed1d434 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -14,3 +14,10 @@
     description: "Enables desktop windowing"
     bug: "304778354"
 }
+
+flag {
+    name: "enable_desktop_windowing_modals_policy"
+    namespace: "lse_desktop_experience"
+    description: "Enables policy for modals activities"
+    bug: "319492844"
+}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 7af1965..5297006 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -400,7 +400,7 @@
         // WindowManager Extensions is an optional shared library that is required for WindowManager
         // Jetpack to fully function. Since it is a widely used library, preload it to improve apps
         // startup performance.
-        if (WindowManager.hasWindowExtensionsEnabled()) {
+        if (WindowManager.HAS_WINDOW_EXTENSIONS_ON_DEVICE) {
             final String systemExtFrameworkPath =
                     new File(Environment.getSystemExtDirectory(), "framework").getPath();
             libs.add(new SharedLibraryInfo(
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 8ddd4ff..d67a630 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -113,7 +113,7 @@
     }
 
     @Nullable
-    private Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
+    Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
         return LocalImageResolver.resolveImage(icon, getContext(), mMaxDrawableWidth,
                 mMaxDrawableHeight);
     }
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 06835f0..6d5a96a 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -37,7 +37,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.text.Spannable;
@@ -1216,7 +1215,7 @@
             return new ConversationHeaderData(
                     conversationText,
                     new OneToOneConversationAvatarData(
-                            resolveAvatarImage(conversationIcon)));
+                            resolveAvatarImageForOneToOne(conversationIcon)));
         }
 
         final List<List<Notification.MessagingStyle.Message>> groupMessages = new ArrayList<>();
@@ -1283,18 +1282,29 @@
 
         return new ConversationHeaderData(
                 conversationText,
-                new GroupConversationAvatarData(resolveAvatarImage(lastIcon),
-                        resolveAvatarImage(secondLastIcon)));
+                new GroupConversationAvatarData(resolveAvatarImageForFacePile(lastIcon),
+                        resolveAvatarImageForFacePile(secondLastIcon)));
     }
 
     /**
-     * {@link ImageResolver#loadImage(Uri)} accepts Uri to load images. However Conversation Avatars
-     * are received as Icon. So, we can't make use of ImageResolver.
+     * One To One Conversation Avatars is loaded by CachingIconView(conversation icon view).
      */
     @Nullable
-    private Drawable resolveAvatarImage(Icon conversationIcon) {
+    private Drawable resolveAvatarImageForOneToOne(Icon conversationIcon) {
         try {
-            return LocalImageResolver.resolveImage(conversationIcon, getContext());
+            return mConversationIconView.loadSizeRestrictedIcon(conversationIcon);
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    /**
+     * Group Avatar drawables are loaded by Icon.
+     */
+    @Nullable
+    private Drawable resolveAvatarImageForFacePile(Icon conversationIcon) {
+        try {
+            return conversationIcon.loadDrawable(getContext());
         } catch (Exception ex) {
             return null;
         }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index a0dc94f..ac961ee 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -266,6 +266,7 @@
                 "fd_utils.cpp",
                 "android_hardware_input_InputWindowHandle.cpp",
                 "android_hardware_input_InputApplicationHandle.cpp",
+                "android_window_InputTransferToken.cpp",
                 "android_window_WindowInfosListener.cpp",
                 "android_window_ScreenCapture.cpp",
                 "jni_common.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aa63f4f..9bbd191 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -221,6 +221,7 @@
 extern int register_android_tracing_PerfettoDataSource(JNIEnv* env);
 extern int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env);
 extern int register_android_tracing_PerfettoProducer(JNIEnv* env);
+extern int register_android_window_InputTransferToken(JNIEnv* env);
 
 // Namespace for Android Runtime flags applied during boot time.
 static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot";
@@ -1678,6 +1679,7 @@
         REG_JNI(register_android_tracing_PerfettoDataSource),
         REG_JNI(register_android_tracing_PerfettoDataSourceInstance),
         REG_JNI(register_android_tracing_PerfettoProducer),
+        REG_JNI(register_android_window_InputTransferToken),
 };
 
 /*
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 07cbaad..3a1e883 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -19,24 +19,23 @@
 
 //#define LOG_NDEBUG 0
 
-#include <inttypes.h>
-
-#include <nativehelper/JNIHelp.h>
-
 #include <android-base/stringprintf.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <input/InputConsumer.h>
 #include <input/InputTransport.h>
+#include <inttypes.h>
 #include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
 #include <utils/Looper.h>
+
 #include <variant>
 #include <vector>
+
 #include "android_os_MessageQueue.h"
 #include "android_view_InputChannel.h"
 #include "android_view_KeyEvent.h"
 #include "android_view_MotionEvent.h"
-
-#include <nativehelper/ScopedLocalRef.h>
-
 #include "core_jni_helpers.h"
 
 namespace android {
diff --git a/core/jni/android_window_InputTransferToken.cpp b/core/jni/android_window_InputTransferToken.cpp
new file mode 100644
index 0000000..60568e30
--- /dev/null
+++ b/core/jni/android_window_InputTransferToken.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputTransferToken"
+
+#include <android_runtime/android_window_InputTransferToken.h>
+#include <gui/InputTransferToken.h>
+#include <nativehelper/JNIHelp.h>
+
+#include "android_os_Parcel.h"
+#include "android_util_Binder.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+    jclass clazz;
+    jfieldID mNativeObject;
+    jmethodID ctor;
+} gInputTransferTokenClassInfo;
+
+static jlong nativeCreate(JNIEnv* env, jclass clazz) {
+    sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make();
+    inputTransferToken->incStrong((void*)nativeCreate);
+    return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static jlong nativeCreateFromBinder(JNIEnv* env, jclass clazz, jobject tokenBinderObj) {
+    if (tokenBinderObj == nullptr) {
+        return 0;
+    }
+    sp<IBinder> token(ibinderForJavaObject(env, tokenBinderObj));
+    if (token == nullptr) {
+        return 0;
+    }
+    sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make(token);
+    inputTransferToken->incStrong((void*)nativeCreate);
+    return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static void nativeWriteToParcel(JNIEnv* env, jclass clazz, jlong nativeObj, jobject parcelObj) {
+    InputTransferToken* inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    inputTransferToken->writeToParcel(parcel);
+}
+
+static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
+    sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make();
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    inputTransferToken->readFromParcel(parcel);
+    inputTransferToken->incStrong((void*)nativeCreate);
+    return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static jobject nativeGetBinderToken(JNIEnv* env, jclass clazz, jlong nativeObj) {
+    sp<InputTransferToken> inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+    return javaObjectForIBinder(env, inputTransferToken->mToken);
+}
+
+InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
+        JNIEnv* env, jobject inputTransferTokenObj) {
+    if (inputTransferTokenObj != nullptr &&
+        env->IsInstanceOf(inputTransferTokenObj, gInputTransferTokenClassInfo.clazz)) {
+        return reinterpret_cast<InputTransferToken*>(
+                env->GetLongField(inputTransferTokenObj,
+                                  gInputTransferTokenClassInfo.mNativeObject));
+    } else {
+        return nullptr;
+    }
+}
+
+jobject android_window_InputTransferToken_getJavaInputTransferToken(
+        JNIEnv* env, const InputTransferToken* inputTransferToken) {
+    if (inputTransferToken == nullptr || env == nullptr) {
+        return nullptr;
+    }
+
+    inputTransferToken->incStrong((void*)nativeCreate);
+    return env->NewObject(gInputTransferTokenClassInfo.clazz, gInputTransferTokenClassInfo.ctor,
+                          reinterpret_cast<jlong>(inputTransferToken));
+}
+
+static void release(InputTransferToken* inputTransferToken) {
+    inputTransferToken->decStrong((void*)nativeCreate);
+}
+
+static jlong nativeGetNativeInputTransferTokenFinalizer(JNIEnv* env, jclass clazz) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&release));
+}
+
+static bool nativeEquals(JNIEnv* env, jclass clazz, jlong inputTransferTokenObj1,
+                         jlong inputTransferTokenObj2) {
+    sp<InputTransferToken> inputTransferToken1(
+            reinterpret_cast<InputTransferToken*>(inputTransferTokenObj1));
+    sp<InputTransferToken> inputTransferToken2(
+            reinterpret_cast<InputTransferToken*>(inputTransferTokenObj2));
+
+    return inputTransferToken1 == inputTransferToken2;
+}
+
+static const JNINativeMethod sInputTransferTokenMethods[] = {
+        // clang-format off
+    {"nativeCreate", "()J", (void*)nativeCreate},
+    {"nativeCreate", "(Landroid/os/IBinder;)J", (void*)nativeCreateFromBinder},
+    {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+    {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel},
+    {"nativeGetBinderToken", "(J)Landroid/os/IBinder;", (void*)nativeGetBinderToken},
+    {"nativeGetNativeInputTransferTokenFinalizer", "()J", (void*)nativeGetNativeInputTransferTokenFinalizer},
+        {"nativeEquals", "(JJ)Z", (void*) nativeEquals},
+        // clang-format on
+};
+
+int register_android_window_InputTransferToken(JNIEnv* env) {
+    int err = RegisterMethodsOrDie(env, "android/window/InputTransferToken",
+                                   sInputTransferTokenMethods, NELEM(sInputTransferTokenMethods));
+    jclass inputTransferTokenClass = FindClassOrDie(env, "android/window/InputTransferToken");
+    gInputTransferTokenClassInfo.clazz = MakeGlobalRefOrDie(env, inputTransferTokenClass);
+    gInputTransferTokenClassInfo.mNativeObject =
+            GetFieldIDOrDie(env, gInputTransferTokenClassInfo.clazz, "mNativeObject", "J");
+    gInputTransferTokenClassInfo.ctor =
+            GetMethodIDOrDie(env, gInputTransferTokenClassInfo.clazz, "<init>", "(J)V");
+    return err;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/include/android_runtime/android_window_InputTransferToken.h b/core/jni/include/android_runtime/android_window_InputTransferToken.h
new file mode 100644
index 0000000..75dbe37
--- /dev/null
+++ b/core/jni/include/android_runtime/android_window_InputTransferToken.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
+#define _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
+
+#include <gui/InputTransferToken.h>
+#include <jni.h>
+
+namespace android {
+
+extern InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
+        JNIEnv* env, jobject inputTransferTokenObj);
+
+extern jobject android_window_InputTransferToken_getJavaInputTransferToken(
+        JNIEnv* env, const InputTransferToken* inputTransferToken);
+
+} // namespace android
+
+#endif // _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
diff --git a/core/res/Android.bp b/core/res/Android.bp
index e2e419f..79b1376 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -161,6 +161,7 @@
         "camera_platform_flags",
         "android.net.platform.flags-aconfig",
         "com.android.window.flags.window-aconfig",
+        "android.permission.flags-aconfig",
     ],
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8ea742d..0869af5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6781,7 +6781,7 @@
          @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
     -->
     <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|privileged" />
 
     <!-- Allows an application to control keyguard.  Only allowed for system processes.
         @hide -->
@@ -6920,7 +6920,8 @@
          projection. This is intended for on device screen recording system app.
          @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") -->
     <permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"
-                android:protectionLevel="signature"/>
+        android:protectionLevel="signature"
+        android:featureFlag="android.permission.flags.sensitive_notification_app_protection"/>
 
     <!-- @SystemApi Allows an application to read install sessions
          @hide This is not a third-party API (intended for system apps). -->
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index 1925588..33f37da 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -25,6 +25,7 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -39,6 +40,7 @@
 import java.lang.reflect.Field;
 
 @RunWith(AndroidJUnit4.class)
+@Presubmit
 @SmallTest
 public class AutomaticZenRuleTest {
     private static final String CLASS = "android.app.AutomaticZenRule";
diff --git a/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
index 625c66a..046f5ac 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
@@ -20,7 +20,7 @@
 import static junit.framework.TestCase.assertTrue;
 
 import android.os.Parcel;
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.Presubmit;
 import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
@@ -35,6 +35,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@Presubmit
 public class NotificationChannelGroupTest {
     private final String CLASS = "android.app.NotificationChannelGroup";
 
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index 56ab034..18209b5 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -46,6 +46,7 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.MediaStore.Audio.AudioColumns;
 import android.test.mock.MockContentResolver;
@@ -74,6 +75,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@Presubmit
 public class NotificationChannelTest {
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
index bd493f4..c44c1eb 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -21,6 +21,7 @@
 import android.app.NotificationHistory.HistoricalNotification;
 import android.graphics.drawable.Icon;
 import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -35,13 +36,19 @@
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
+@Presubmit
 public class NotificationHistoryTest {
 
-    private HistoricalNotification getHistoricalNotification(int index) {
+    private static HistoricalNotification getHistoricalNotification(int index) {
         return getHistoricalNotification("package" + index, index);
     }
 
-    private HistoricalNotification getHistoricalNotification(String packageName, int index) {
+    private static HistoricalNotification getHistoricalNotification(String packageName, int index) {
+        return getHistoricalNotification(packageName, index, /* includeIcon= */ true);
+    }
+
+    private static HistoricalNotification getHistoricalNotification(String packageName, int index,
+            boolean includeIcon) {
         String expectedChannelName = "channelName" + index;
         String expectedChannelId = "channelId" + index;
         int expectedUid = 1123456 + index;
@@ -65,7 +72,7 @@
                 .setPostedTimeMs(expectedPostTime)
                 .setTitle(expectedTitle)
                 .setText(expectedText)
-                .setIcon(expectedIcon)
+                .setIcon(includeIcon ? expectedIcon : null)
                 .setConversationId(conversationId)
                 .build();
     }
@@ -376,7 +383,8 @@
 
         List<HistoricalNotification> expectedEntries = new ArrayList<>();
         for (int i = 10; i >= 1; i--) {
-            HistoricalNotification n = getHistoricalNotification(i);
+            HistoricalNotification n = getHistoricalNotification("packageName" + i,
+                    i, /* includeIcon= */ false);
             expectedEntries.add(n);
             history.addNotificationToWrite(n);
         }
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 5b0502d..9a41fe0 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -85,6 +85,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemProperties;
+import android.platform.test.annotations.Presubmit;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
@@ -107,6 +108,7 @@
 import libcore.junit.util.compat.CoreCompatChangeRule;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -117,6 +119,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@Presubmit
 public class NotificationTest {
 
     private Context mContext;
@@ -768,6 +771,7 @@
     }
 
     @Test
+    @Ignore // TODO: b/329389261 - Restore or delete
     public void testColors_ensureColors_dayMode_producesValidPalette() {
         Notification.Colors c = new Notification.Colors();
         boolean colorized = false;
@@ -796,6 +800,7 @@
     }
 
     @Test
+    @Ignore // TODO: b/329389261 - Restore or delete
     public void testColors_ensureColors_colorized_producesValidPalette_red() {
         validateColorizedPaletteForColor(Color.RED);
     }
@@ -1244,6 +1249,7 @@
     }
 
     @Test
+    @Ignore // TODO: b/329402256 - Restore or delete
     public void testBigPictureStyle_setExtras_pictureIconNull_pictureIconKeyNull() {
         Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
         bpStyle.bigPicture((Bitmap) null);
@@ -1257,6 +1263,7 @@
     }
 
     @Test
+    @Ignore // TODO: b/329402256 - Restore or delete
     public void testBigPictureStyle_setExtras_pictureIconNull_pictureKeyNull() {
         Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
         bpStyle.bigPicture((Bitmap) null);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 2544fcb..652011b 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -679,20 +679,20 @@
         }
 
         sInstrumentation.runOnMainSync(() -> {
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
                     FRAME_RATE_CATEGORY_HIGH_HINT);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
         });
     }
diff --git a/core/tests/coretests/src/android/view/WindowManagerTests.java b/core/tests/coretests/src/android/view/WindowManagerTests.java
new file mode 100644
index 0000000..c5a9d48
--- /dev/null
+++ b/core/tests/coretests/src/android/view/WindowManagerTests.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static com.android.window.flags.Flags.FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link WindowManager}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:WindowManagerTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowManagerTests {
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Test
+    public void testHasWindowExtensionsEnabled_flagDisabled() {
+        mSetFlagsRule.disableFlags(FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG);
+
+        // Before FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG, Extensions are always bundled with AE.
+        assertEquals(isActivityEmbeddingEnableForAll(),
+                WindowManager.hasWindowExtensionsEnabled());
+    }
+
+    @Test
+    public void testHasWindowExtensionsEnabled_flagEnabled() {
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_WM_EXTENSIONS_FOR_ALL_FLAG);
+
+        // Extensions should be enabled on all devices.
+        assertTrue(WindowManager.hasWindowExtensionsEnabled());
+    }
+
+    @Test
+    public void testActivityEmbeddingAvailability() {
+        assumeTrue(isActivityEmbeddingEnableForAll());
+
+        // AE can only be enabled when extensions is enabled.
+        assertTrue(WindowManager.hasWindowExtensionsEnabled());
+    }
+
+    private static boolean isActivityEmbeddingEnableForAll() {
+        return !WindowManager.ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15;
+    }
+}
diff --git a/data/etc/enhanced-confirmation.xml b/data/etc/enhanced-confirmation.xml
index 3b1867c..973bcb5 100644
--- a/data/etc/enhanced-confirmation.xml
+++ b/data/etc/enhanced-confirmation.xml
@@ -24,33 +24,49 @@
     <enhanced-confirmation-trusted-package
          package="com.example.app"
          sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
-
     ...
 
     <enhanced-confirmation-trusted-installer
          package="com.example.installer"
          sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
-
     ...
 
-The "enhanced-confirmation-trusted-package" entry shown above indicates that "com.example.app"
-should be considered a "trusted package". A "trusted package" will be exempt from ECM restrictions.
+The <enhanced-confirmation-trusted-package> entry shown in the above example indicates that
+"com.example.app" should be considered a "trusted package". A "trusted package" will be exempt from
+ECM restrictions.
 
-The "enhanced-confirmation-trusted-installer" entry shown above indicates that
-"com.example.installer" should be considered a "trusted installer". A "trusted installer", and all
-packages that it installs, will be exempt from ECM restrictions. (There are some exceptions to this.
-For example, a trusted installer, at the time of installing an app, can opt the app back in to ECM
-restrictions by setting the app's package source to PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
-or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE.)
+The <enhanced-confirmation-trusted-installer> entry shown in the above example indicates that
+"com.example.app" should be considered a "trusted installer". Apps installed by "trusted installers"
+will be exempt from ECM restrictions, with conditions explained in the next few paragraphs.
 
-In either case:
+If zero <enhanced-confirmation-trusted-installer> entries are declared, then *all* packages will be
+exempt from ECM restrictions, except apps meeting *all* of the following criteria:
+
+    A. The app is not pre-installed, and
+    B. The app has no matching <enhanced-confirmation-trusted-package> entries declared, and
+    C. The app is marked by its installer as coming from an untrustworthy package source.
+
+(For example, an installer may set an app's package source to
+PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE,
+which are considered untrustworthy.)
+
+If one or more <enhanced-confirmation-trusted-installer> entries are declared, then packages must,
+in order to be exempt from ECM, meet at least one of the following criteria:
+
+    A. Be installed by an installer with a matching <enhanced-confirmation-trusted-installer> entry
+       declared, and be marked as coming from an "trustworthy" package source by the installer, or
+    B. Be installed via a pre-installed installer, and be marked as coming from a "trustworthy"
+       package source by the installer, or
+    C. Have a matching <enhanced-confirmation-trusted-package> entry declared.
+
+For either type of XML element:
 
 - The "package" XML attribute refers to the app's package name.
 - The "sha256-cert-digest" XML attribute refers to the SHA-256 hash of an app signing certificate.
 
 For any entry to successfully apply to a package, both XML attributes must be present, and must
 match the package. That is, the package name must match the "package" attribute, and the app must be
-signed by the signing certificate identified by the "sha256-cert-digest" attribute..
+signed by the signing certificate identified by the "sha256-cert-digest" attribute.
 -->
 
 <config></config>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0f12438..749f0e1 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -553,6 +553,8 @@
         <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
         <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
         <permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/>
+        <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+        <permission name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
         <!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
         <permission name="android.permission.WRITE_APN_SETTINGS"/>
         <!-- Permission required for GTS test - GtsStatsdHostTestCases -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0231d3a..1aa8af5 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2665,12 +2665,6 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/Task.java"
     },
-    "4446998544419008924": {
-      "message": "Moving to RESUMED: %s (starting new instance) callers=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "4037728373502324767": {
       "message": "resumeNextFocusableActivityWhenRootTaskIsEmpty: %s, go home",
       "level": "DEBUG",
@@ -2767,12 +2761,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
-    "2088177629189452176": {
-      "message": "Activity config changed during resume: %s, new next: %s",
-      "level": "INFO",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/TaskFragment.java"
-    },
     "-8483536760290526299": {
       "message": "resumeTopActivity: Resumed %s",
       "level": "DEBUG",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 6714263..9756278 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -16,15 +16,19 @@
 
 package androidx.window.extensions;
 
-import android.app.ActivityTaskManager;
+import static android.view.WindowManager.ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15;
+import static android.view.WindowManager.ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15;
+
 import android.app.ActivityThread;
 import android.app.Application;
+import android.app.compat.CompatChanges;
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
 import androidx.window.common.RawFoldingFeatureProducer;
 import androidx.window.extensions.area.WindowAreaComponent;
@@ -38,25 +42,38 @@
 
 
 /**
- * The reference implementation of {@link WindowExtensions} that implements the initial API version.
+ * The reference implementation of {@link WindowExtensions} that implements the latest WindowManager
+ * Extensions APIs.
  */
-public class WindowExtensionsImpl implements WindowExtensions {
+class WindowExtensionsImpl implements WindowExtensions {
 
     private static final String TAG = "WindowExtensionsImpl";
+
+    /**
+     * The min version of the WM Extensions that must be supported in the current platform version.
+     */
+    @VisibleForTesting
+    static final int EXTENSIONS_VERSION_CURRENT_PLATFORM = 5;
+
     private final Object mLock = new Object();
     private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer;
     private volatile WindowLayoutComponentImpl mWindowLayoutComponent;
     private volatile SplitController mSplitController;
     private volatile WindowAreaComponent mWindowAreaComponent;
 
-    public WindowExtensionsImpl() {
-        Log.i(TAG, "Initializing Window Extensions.");
+    private final int mVersion = EXTENSIONS_VERSION_CURRENT_PLATFORM;
+    private final boolean mIsActivityEmbeddingEnabled;
+
+    WindowExtensionsImpl() {
+        mIsActivityEmbeddingEnabled = isActivityEmbeddingEnabled();
+        Log.i(TAG, "Initializing Window Extensions, vendor API level=" + mVersion
+                + ", activity embedding enabled=" + mIsActivityEmbeddingEnabled);
     }
 
     // TODO(b/241126279) Introduce constants to better version functionality
     @Override
     public int getVendorApiLevel() {
-        return 5;
+        return mVersion;
     }
 
     @NonNull
@@ -74,8 +91,8 @@
         if (mFoldingFeatureProducer == null) {
             synchronized (mLock) {
                 if (mFoldingFeatureProducer == null) {
-                    Context context = getApplication();
-                    RawFoldingFeatureProducer foldingFeatureProducer =
+                    final Context context = getApplication();
+                    final RawFoldingFeatureProducer foldingFeatureProducer =
                             new RawFoldingFeatureProducer(context);
                     mFoldingFeatureProducer =
                             new DeviceStateManagerFoldingFeatureProducer(context,
@@ -91,8 +108,8 @@
         if (mWindowLayoutComponent == null) {
             synchronized (mLock) {
                 if (mWindowLayoutComponent == null) {
-                    Context context = getApplication();
-                    DeviceStateManagerFoldingFeatureProducer producer =
+                    final Context context = getApplication();
+                    final DeviceStateManagerFoldingFeatureProducer producer =
                             getFoldingFeatureProducer();
                     mWindowLayoutComponent = new WindowLayoutComponentImpl(context, producer);
                 }
@@ -102,29 +119,35 @@
     }
 
     /**
-     * Returns a reference implementation of {@link WindowLayoutComponent} if available,
-     * {@code null} otherwise. The implementation must match the API level reported in
-     * {@link WindowExtensions#getWindowLayoutComponent()}.
+     * Returns a reference implementation of the latest {@link WindowLayoutComponent}.
+     *
+     * The implementation must match the API level reported in
+     * {@link WindowExtensions#getVendorApiLevel()}.
+     *
      * @return {@link WindowLayoutComponent} OEM implementation
      */
+    @NonNull
     @Override
     public WindowLayoutComponent getWindowLayoutComponent() {
         return getWindowLayoutComponentImpl();
     }
 
     /**
-     * Returns a reference implementation of {@link ActivityEmbeddingComponent} if available,
-     * {@code null} otherwise. The implementation must match the API level reported in
-     * {@link WindowExtensions#getWindowLayoutComponent()}.
+     * Returns a reference implementation of the latest {@link ActivityEmbeddingComponent} if the
+     * device supports this feature, {@code null} otherwise.
+     *
+     * The implementation must match the API level reported in
+     * {@link WindowExtensions#getVendorApiLevel()}.
+     *
      * @return {@link ActivityEmbeddingComponent} OEM implementation.
      */
     @Nullable
+    @Override
     public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
+        if (!mIsActivityEmbeddingEnabled) {
+            return null;
+        }
         if (mSplitController == null) {
-            if (!ActivityTaskManager.supportsMultiWindow(getApplication())) {
-                // Disable AE for device that doesn't support multi window.
-                return null;
-            }
             synchronized (mLock) {
                 if (mSplitController == null) {
                     mSplitController = new SplitController(
@@ -138,21 +161,35 @@
     }
 
     /**
-     * Returns a reference implementation of {@link WindowAreaComponent} if available,
-     * {@code null} otherwise. The implementation must match the API level reported in
-     * {@link WindowExtensions#getWindowAreaComponent()}.
+     * Returns a reference implementation of the latest {@link WindowAreaComponent}
+     *
+     * The implementation must match the API level reported in
+     * {@link WindowExtensions#getVendorApiLevel()}.
+     *
      * @return {@link WindowAreaComponent} OEM implementation.
      */
+    @Nullable
+    @Override
     public WindowAreaComponent getWindowAreaComponent() {
         if (mWindowAreaComponent == null) {
             synchronized (mLock) {
                 if (mWindowAreaComponent == null) {
-                    Context context = ActivityThread.currentApplication();
-                    mWindowAreaComponent =
-                            new WindowAreaComponentImpl(context);
+                    final Context context = getApplication();
+                    mWindowAreaComponent = new WindowAreaComponentImpl(context);
                 }
             }
         }
         return mWindowAreaComponent;
     }
+
+    @VisibleForTesting
+    static boolean isActivityEmbeddingEnabled() {
+        if (!ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15) {
+            // Device enables it for all apps without targetSDK check.
+            // This must be true for all large screen devices.
+            return true;
+        }
+        // Use compat framework to guard the feature with targetSDK 15.
+        return CompatChanges.isChangeEnabled(ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15);
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java
index f9e1f07..b0a0c4d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java
@@ -17,13 +17,19 @@
 package androidx.window.extensions;
 
 import android.annotation.NonNull;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+import androidx.window.extensions.area.WindowAreaComponent;
+import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
+import androidx.window.extensions.layout.WindowLayoutComponent;
 
 /**
  * Provides the OEM implementation of {@link WindowExtensions}.
  */
 public class WindowExtensionsProvider {
 
-    private static final WindowExtensions sWindowExtensions = new WindowExtensionsImpl();
+    private static volatile WindowExtensions sWindowExtensions;
 
     /**
      * Returns the OEM implementation of {@link WindowExtensions}. This method is implemented in
@@ -33,6 +39,44 @@
      */
     @NonNull
     public static WindowExtensions getWindowExtensions() {
+        if (sWindowExtensions == null) {
+            synchronized (WindowExtensionsProvider.class) {
+                if (sWindowExtensions == null) {
+                    sWindowExtensions = WindowManager.hasWindowExtensionsEnabled()
+                            ? new WindowExtensionsImpl()
+                            : new DisabledWindowExtensions();
+                }
+            }
+        }
         return sWindowExtensions;
     }
+
+    /**
+     * The stub version to return when the WindowManager Extensions is disabled
+     * @see WindowManager#hasWindowExtensionsEnabled
+     */
+    private static class DisabledWindowExtensions implements WindowExtensions {
+        @Override
+        public int getVendorApiLevel() {
+            return 0;
+        }
+
+        @Nullable
+        @Override
+        public WindowLayoutComponent getWindowLayoutComponent() {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public WindowAreaComponent getWindowAreaComponent() {
+            return null;
+        }
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1abda42..0cc4b1f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -88,7 +88,7 @@
 import androidx.window.common.CommonFoldingFeature;
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
-import androidx.window.extensions.WindowExtensionsImpl;
+import androidx.window.extensions.WindowExtensions;
 import androidx.window.extensions.core.util.function.Consumer;
 import androidx.window.extensions.core.util.function.Function;
 import androidx.window.extensions.core.util.function.Predicate;
@@ -410,7 +410,7 @@
      * Registers the split organizer callback to notify about changes to active splits.
      *
      * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with
-     * {@link WindowExtensionsImpl#getVendorApiLevel()} 2.
+     * {@link WindowExtensions#getVendorApiLevel()} 2.
      */
     @Deprecated
     @Override
@@ -423,7 +423,7 @@
     /**
      * Registers the split organizer callback to notify about changes to active splits.
      *
-     * @since {@link WindowExtensionsImpl#getVendorApiLevel()} 2
+     * @since {@link WindowExtensions#getVendorApiLevel()} 2
      */
     @Override
     public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index f471af0..4267749 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -16,12 +16,15 @@
 
 package androidx.window.extensions;
 
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static androidx.window.extensions.WindowExtensionsImpl.EXTENSIONS_VERSION_CURRENT_PLATFORM;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.app.ActivityTaskManager;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
 import android.platform.test.annotations.Presubmit;
+import android.view.WindowManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -42,25 +45,61 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class WindowExtensionsTest {
+
     private WindowExtensions mExtensions;
+    private int mVersion;
 
     @Before
     public void setUp() {
         mExtensions = WindowExtensionsProvider.getWindowExtensions();
+        mVersion = mExtensions.getVendorApiLevel();
     }
 
     @Test
-    public void testGetWindowLayoutComponent() {
+    public void testGetVendorApiLevel_extensionsEnabled_matchesCurrentVersion() {
+        assumeTrue(WindowManager.hasWindowExtensionsEnabled());
+        assertThat(mVersion).isEqualTo(EXTENSIONS_VERSION_CURRENT_PLATFORM);
+    }
+
+    @Test
+    public void testGetVendorApiLevel_extensionsDisabled_returnsZero() {
+        assumeFalse(WindowManager.hasWindowExtensionsEnabled());
+        assertThat(mVersion).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetWindowLayoutComponent_extensionsEnabled_returnsImplementation() {
+        assumeTrue(WindowManager.hasWindowExtensionsEnabled());
         assertThat(mExtensions.getWindowLayoutComponent()).isNotNull();
     }
 
     @Test
-    public void testGetActivityEmbeddingComponent() {
-        if (ActivityTaskManager.supportsMultiWindow(getInstrumentation().getContext())) {
-            assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
-        } else {
-            assertThat(mExtensions.getActivityEmbeddingComponent()).isNull();
-        }
+    public void testGetWindowLayoutComponent_extensionsDisabled_returnsNull() {
+        assumeFalse(WindowManager.hasWindowExtensionsEnabled());
+        assertThat(mExtensions.getWindowLayoutComponent()).isNull();
+    }
+    @Test
+    public void testGetActivityEmbeddingComponent_featureDisabled_returnsNull() {
+        assumeFalse(WindowExtensionsImpl.isActivityEmbeddingEnabled());
+        assertThat(mExtensions.getActivityEmbeddingComponent()).isNull();
+    }
+
+    @Test
+    public void testGetActivityEmbeddingComponent_featureEnabled_returnsImplementation() {
+        assumeTrue(WindowExtensionsImpl.isActivityEmbeddingEnabled());
+        assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
+    }
+
+    @Test
+    public void testGetWindowAreaComponent_extensionsEnabled_returnsImplementation() {
+        assumeTrue(WindowManager.hasWindowExtensionsEnabled());
+        assertThat(mExtensions.getWindowAreaComponent()).isNotNull();
+    }
+
+    @Test
+    public void testGetWindowAreaComponent_extensionsDisabled_returnsNull() {
+        assumeFalse(WindowManager.hasWindowExtensionsEnabled());
+        assertThat(mExtensions.getWindowAreaComponent()).isNull();
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 23bdd08..6524c96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -449,17 +449,21 @@
 
                 @Override
                 public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
-                        @NonNull MagnetizedObject draggedObject) {
-                    if (draggedObject.getUnderlyingObject() instanceof View view) {
+                        @NonNull MagnetizedObject<?> draggedObject) {
+                    Object underlyingObject = draggedObject.getUnderlyingObject();
+                    if (underlyingObject instanceof View) {
+                        View view = (View) underlyingObject;
                         animateDismissBubble(view, true);
                     }
                 }
 
                 @Override
                 public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
-                        @NonNull MagnetizedObject draggedObject,
+                        @NonNull MagnetizedObject<?> draggedObject,
                         float velX, float velY, boolean wasFlungOut) {
-                    if (draggedObject.getUnderlyingObject() instanceof View view) {
+                    Object underlyingObject = draggedObject.getUnderlyingObject();
+                    if (underlyingObject instanceof View) {
+                        View view = (View) underlyingObject;
                         animateDismissBubble(view, false);
 
                         if (wasFlungOut) {
@@ -474,7 +478,9 @@
                 @Override
                 public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
                         @NonNull MagnetizedObject<?> draggedObject) {
-                    if (draggedObject.getUnderlyingObject() instanceof View view) {
+                    Object underlyingObject = draggedObject.getUnderlyingObject();
+                    if (underlyingObject instanceof View) {
+                        View view = (View) underlyingObject;
                         mExpandedAnimationController.dismissDraggedOutBubble(
                                 view /* bubble */,
                                 mDismissView.getHeight() /* translationYBy */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index fb0ed15..6a3c8d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -138,9 +138,9 @@
             @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
         final Region region = new Region();
         int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
-                ? 2 * layout.stableInsets().top
-                : mContext.getResources().getDimensionPixelSize(
-                        com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
+                ? mContext.getResources().getDimensionPixelSize(
+                com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+                : 2 * layout.stableInsets().top;
         // A thin, short Rect at the top of the screen.
         if (windowingMode == WINDOWING_MODE_FREEFORM) {
             int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index dd8c1a0..4a3130f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -78,6 +78,7 @@
 import com.android.wm.shell.transition.OneShotRemoteHandler
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
 import java.io.PrintWriter
@@ -960,7 +961,8 @@
     }
 
     /**
-     * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+     * Perform checks required on drag end. If indicator indicates a windowing mode change, perform
+     * that change. Otherwise, ensure bounds are up to date.
      *
      * @param taskInfo the task being dragged.
      * @param position position of surface when drag ends.
@@ -971,7 +973,8 @@
         taskInfo: RunningTaskInfo,
         position: Point,
         inputCoordinate: PointF,
-        taskBounds: Rect
+        taskBounds: Rect,
+        validDragArea: Rect
     ) {
         if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
             return
@@ -994,10 +997,21 @@
                 releaseVisualIndicator()
                 snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
             }
-            DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
             DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+                // If task bounds are outside valid drag area, snap them inward and perform a
+                // transaction to set bounds.
+                if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+                        taskBounds, validDragArea)) {
+                    val wct = WindowContainerTransaction()
+                    wct.setBounds(taskInfo.token, taskBounds)
+                    transitions.startTransition(TRANSIT_CHANGE, wct, null)
+                }
                 releaseVisualIndicator()
             }
+            DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+                throw IllegalArgumentException("Should not be receiving TO_DESKTOP_INDICATOR for " +
+                        "a freeform task.")
+            }
         }
         // A freeform drag-move ended, remove the indicator immediately.
         releaseVisualIndicator()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index ad40493..f7ffdd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -64,7 +64,9 @@
         default void onSplitVisibilityChanged(boolean visible) {}
     }
 
-    /** Callback interface for listening to requests to enter split select */
+    /**
+     * Callback interface for listening to requests to enter split select. Used for desktop -> split
+     */
     interface SplitSelectListener {
         default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
                 int splitPosition, Rect taskBounds) {
@@ -72,6 +74,15 @@
         }
     }
 
+    interface SplitInvocationListener {
+        /**
+         * Called whenever shell starts or stops the split screen animation
+         * @param animationRunning if {@code true} the animation has begun, if {@code false} the
+         *                         animation has finished
+         */
+        default void onSplitAnimationInvoked(boolean animationRunning) { }
+    }
+
     /** Registers listener that gets split screen callback. */
     void registerSplitScreenListener(@NonNull SplitScreenListener listener,
             @NonNull Executor executor);
@@ -79,6 +90,15 @@
     /** Unregisters listener that gets split screen callback. */
     void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
 
+    /**
+     * Registers a {@link SplitInvocationListener} to notify when the animation to enter split
+     * screen has started and stopped
+     *
+     * @param executor callbacks to the listener will be executed on this executor
+     */
+    void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
+            @NonNull Executor executor);
+
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 86c8f04..218abe2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -1138,6 +1138,12 @@
         }
 
         @Override
+        public void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
+                @NonNull Executor executor) {
+            mStageCoordinator.registerSplitAnimationListener(listener, executor);
+        }
+
+        @Override
         public void onFinishedWakingUp() {
             mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 1a53a1d..e69ff70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 /** Manages transition animations for split-screen. */
 class SplitScreenTransitions {
@@ -79,6 +80,8 @@
 
     private Transitions.TransitionFinishCallback mFinishCallback = null;
     private SurfaceControl.Transaction mFinishTransaction;
+    private SplitScreen.SplitInvocationListener mSplitInvocationListener;
+    private Executor mSplitInvocationListenerExecutor;
 
     SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
             @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) {
@@ -353,6 +356,10 @@
                     + " skip to start enter split transition since it already exist. ");
             return null;
         }
+        if (mSplitInvocationListenerExecutor != null && mSplitInvocationListener != null) {
+            mSplitInvocationListenerExecutor.execute(() -> mSplitInvocationListener
+                    .onSplitAnimationInvoked(true /*animationRunning*/));
+        }
         final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
         setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim);
         return transition;
@@ -529,6 +536,12 @@
         mTransitions.getAnimExecutor().execute(va::start);
     }
 
+    public void registerSplitAnimListener(@NonNull SplitScreen.SplitInvocationListener listener,
+            @NonNull Executor executor) {
+        mSplitInvocationListener = listener;
+        mSplitInvocationListenerExecutor = executor;
+    }
+
     /** Calls when the transition got consumed. */
     interface TransitionConsumedCallback {
         void onConsumed(boolean aborted);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 36368df9..8ab6cf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -156,6 +156,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -236,6 +237,9 @@
     private DefaultMixedHandler mMixedHandler;
     private final Toast mSplitUnsupportedToast;
     private SplitRequest mSplitRequest;
+    /** Used to notify others of when shell is animating into split screen */
+    private SplitScreen.SplitInvocationListener mSplitInvocationListener;
+    private Executor mSplitInvocationListenerExecutor;
 
     /**
      * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support
@@ -246,6 +250,14 @@
         return false;
     }
 
+    /** NOTE: Will overwrite any previously set {@link #mSplitInvocationListener} */
+    public void registerSplitAnimationListener(
+            @NonNull SplitScreen.SplitInvocationListener listener, @NonNull Executor executor) {
+        mSplitInvocationListener = listener;
+        mSplitInvocationListenerExecutor = executor;
+        mSplitTransitions.registerSplitAnimListener(listener, executor);
+    }
+
     class SplitRequest {
         @SplitPosition
         int mActivatePosition;
@@ -529,7 +541,7 @@
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
                     Log.w(TAG, splitFailureMessage("startShortcut",
                             "side stage was not populated"));
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
 
                 if (finishedCallback != null) {
@@ -660,7 +672,7 @@
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
                     Log.w(TAG, splitFailureMessage("startIntentLegacy",
                             "side stage was not populated"));
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
 
                 if (apps != null) {
@@ -1213,7 +1225,7 @@
                             ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
             Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
                     "main or side stage was not populated."));
-            mSplitUnsupportedToast.show();
+            handleUnsupportedSplitStart();
         } else {
             mSyncQueue.queue(evictWct);
             mSyncQueue.runInSync(t -> {
@@ -1234,7 +1246,7 @@
                     ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
             Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
                     "main or side stage was not populated"));
-            mSplitUnsupportedToast.show();
+            handleUnsupportedSplitStart();
             return;
         }
 
@@ -2801,6 +2813,7 @@
             if (hasEnteringPip) {
                 mMixedHandler.animatePendingEnterPipFromSplit(transition, info,
                         startTransaction, finishTransaction, finishCallback);
+                notifySplitAnimationFinished();
                 return true;
             }
 
@@ -2835,6 +2848,7 @@
                 //                    the transition, or synchronize task-org callbacks.
             }
             // Use normal animations.
+            notifySplitAnimationFinished();
             return false;
         } else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) {
             // A display-change has been un-expectedly inserted into the transition. Redirect
@@ -2848,6 +2862,7 @@
                     mSplitLayout.update(startTransaction, true /* resetImePosition */);
                     startTransaction.apply();
                 }
+                notifySplitAnimationFinished();
                 return true;
             }
         }
@@ -3021,7 +3036,7 @@
                     pendingEnter.mRemoteHandler.onTransitionConsumed(transition,
                             false /*aborted*/, finishT);
                 }
-                mSplitUnsupportedToast.show();
+                handleUnsupportedSplitStart();
                 return true;
             }
         }
@@ -3050,6 +3065,7 @@
         final TransitionInfo.Change finalMainChild = mainChild;
         final TransitionInfo.Change finalSideChild = sideChild;
         enterTransition.setFinishedCallback((callbackWct, callbackT) -> {
+            notifySplitAnimationFinished();
             if (finalMainChild != null) {
                 if (!mainNotContainOpenTask) {
                     mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId);
@@ -3466,6 +3482,19 @@
                 mSplitLayout.isLeftRightSplit());
     }
 
+    private void handleUnsupportedSplitStart() {
+        mSplitUnsupportedToast.show();
+        notifySplitAnimationFinished();
+    }
+
+    private void notifySplitAnimationFinished() {
+        if (mSplitInvocationListener == null || mSplitInvocationListenerExecutor == null) {
+            return;
+        }
+        mSplitInvocationListenerExecutor.execute(() ->
+                mSplitInvocationListener.onSplitAnimationInvoked(false /*animationRunning*/));
+    }
+
     /**
      * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
      * executed.
@@ -3528,7 +3557,7 @@
                 if (!ENABLE_SHELL_TRANSITIONS) {
                     StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
                             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                     return;
                 }
 
@@ -3548,7 +3577,7 @@
                         "app package " + taskInfo.baseActivity.getPackageName()
                         + " does not support splitscreen, or is a controlled activity type"));
                 if (splitScreenVisible) {
-                    mSplitUnsupportedToast.show();
+                    handleUnsupportedSplitStart();
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 1ce87ef..4465aef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE;
 import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
 import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
 
@@ -270,21 +271,18 @@
 
         @Override
         public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
-            if (immediately) {
+            if (immediately
+                    // Show the latest content as soon as possible for unlocking to home.
+                    || mActivityType == ACTIVITY_TYPE_HOME
+                    || info.deferRemoveMode == DEFER_MODE_NONE) {
                 removeImmediately();
-            } else {
-                scheduleRemove(info.deferRemoveForImeMode);
-                return false;
+                return true;
             }
-            return true;
+            scheduleRemove(info.deferRemoveMode);
+            return false;
         }
 
         void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) {
-            // Show the latest content as soon as possible for unlocking to home.
-            if (mActivityType == ACTIVITY_TYPE_HOME) {
-                removeImmediately();
-                return;
-            }
             mRemoveExecutor.removeCallbacks(mScheduledRunnable);
             final long delayRemovalTime;
             switch (deferRemoveForImeMode) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 4ea71490..5b402a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -212,6 +212,7 @@
         switch (mType) {
             case TYPE_RECENTS_DURING_DESKTOP:
             case TYPE_RECENTS_DURING_SPLIT:
+            case TYPE_RECENTS_DURING_KEYGUARD:
                 mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
                 break;
             default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
index 7a50814..564e716 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
@@ -31,42 +31,42 @@
     companion object {
         /** @see [com.android.internal.protolog.common.ProtoLog.d] */
         fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.d(group.tag, String.format(messageString, *args))
             }
         }
 
         /** @see [com.android.internal.protolog.common.ProtoLog.v] */
         fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.v(group.tag, String.format(messageString, *args))
             }
         }
 
         /** @see [com.android.internal.protolog.common.ProtoLog.i] */
         fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.i(group.tag, String.format(messageString, *args))
             }
         }
 
         /** @see [com.android.internal.protolog.common.ProtoLog.w] */
         fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.w(group.tag, String.format(messageString, *args))
             }
         }
 
         /** @see [com.android.internal.protolog.common.ProtoLog.e] */
         fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.e(group.tag, String.format(messageString, *args))
             }
         }
 
         /** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
         fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
-            if (ProtoLog.isEnabled(group)) {
+            if (group.isLogToLogcat) {
                 Log.wtf(group.tag, String.format(messageString, *args))
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index b2eeea7..c59a1b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,9 +19,11 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.util.SparseArray;
 import android.view.Choreographer;
@@ -186,7 +188,7 @@
 
         final FluidResizeTaskPositioner taskPositioner =
                 new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
-                        mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
+                        mDisplayController);
         final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -286,8 +288,15 @@
                         mDragPointerId = e.getPointerId(0);
                     }
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    mDragPositioningCallback.onDragPositioningEnd(
+                    final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+                    DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(newTaskBounds,
+                            mWindowDecorByTaskId.get(mTaskId).calculateValidDragArea());
+                    if (newTaskBounds != taskInfo.configuration.windowConfiguration.getBounds()) {
+                        final WindowContainerTransaction wct = new WindowContainerTransaction();
+                        wct.setBounds(taskInfo.token, newTaskBounds);
+                        mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+                    }
                     final boolean wasDragging = mIsDragging;
                     mIsDragging = false;
                     return wasDragging;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 91e9601..9a48922 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.windowdecor;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
@@ -87,6 +88,7 @@
     }
 
     @Override
+    @NonNull
     Rect calculateValidDragArea() {
         final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
                 R.dimen.caption_left_buttons_width);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 98ff0ee..918cefb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -639,7 +639,7 @@
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
                             new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
-                            newTaskBounds);
+                            newTaskBounds, decoration.calculateValidDragArea());
                     if (touchingButton && !mHasLongClicked) {
                         // We need the input event to not be consumed here to end the ripple
                         // effect on the touched button. We will reset drag state in the ensuing
@@ -867,8 +867,9 @@
                 }
                 if (mTransitionDragActive) {
                     // Do not create an indicator at all if we're not past transition height.
-                    if (ev.getRawY() < mContext.getResources().getDimensionPixelSize(com.android
-                            .wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+                    DisplayLayout layout = mDisplayController
+                            .getDisplayLayout(relevantDecor.mTaskInfo.displayId);
+                    if (ev.getRawY() < 2 * layout.stableInsets().top
                             && mMoveToDesktopAnimator == null) {
                         return;
                     }
@@ -1086,18 +1087,16 @@
         windowDecoration.createResizeVeil();
 
         final DragPositioningCallback dragPositioningCallback;
-        final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.desktop_mode_fullscreen_from_desktop_height);
         if (!DesktopModeStatus.isVeiledResizeEnabled()) {
             dragPositioningCallback =  new FluidResizeTaskPositioner(
                     mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
-                    mDragStartListener, mTransactionFactory, transitionAreaHeight);
+                    mDragStartListener, mTransactionFactory);
             windowDecoration.setTaskDragResizer(
                     (FluidResizeTaskPositioner) dragPositioningCallback);
         } else {
             dragPositioningCallback =  new VeiledResizeTaskPositioner(
                     mTaskOrganizer, windowDecoration, mDisplayController,
-                    mDragStartListener, mTransitions, transitionAreaHeight);
+                    mDragStartListener, mTransitions);
             windowDecoration.setTaskDragResizer(
                     (VeiledResizeTaskPositioner) dragPositioningCallback);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index c9669a7..4c9e171 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -22,6 +22,7 @@
 
 import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Context;
@@ -499,6 +500,7 @@
      * Determine valid drag area for this task based on elements in the app chip.
      */
     @Override
+    @NonNull
     Rect calculateValidDragArea() {
         final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
                 mWindowDecorViewHolder).getAppNameTextWidth();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 5afbd54..82c399a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -131,7 +131,7 @@
         t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
     }
 
-    private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+    static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
             PointF repositionStartPoint, float x, float y) {
         final float deltaX = x - repositionStartPoint.x;
         final float deltaY = y - repositionStartPoint.y;
@@ -140,49 +140,32 @@
     }
 
     /**
-     * Calculates the new position of the top edge of the task and returns true if it is below the
-     * disallowed area.
-     *
-     * @param disallowedAreaForEndBoundsHeight the height of the area that where the task positioner
-     *                                         should not finalize the bounds using WCT#setBounds
-     * @param taskBoundsAtDragStart the bounds of the task on the first drag input event
-     * @param repositionStartPoint initial input coordinate
-     * @param y the y position of the motion event
-     * @return true if the top of the task is below the disallowed area
+     * If task bounds are outside of provided drag area, snap the bounds to be just inside the
+     * drag area.
+     * @param repositionTaskBounds bounds determined by task positioner
+     * @param validDragArea the area that task must be positioned inside
+     * @return whether bounds were modified
      */
-    static boolean isBelowDisallowedArea(int disallowedAreaForEndBoundsHeight,
-            Rect taskBoundsAtDragStart, PointF repositionStartPoint, float y) {
-        final float deltaY = y - repositionStartPoint.y;
-        final float topPosition = taskBoundsAtDragStart.top + deltaY;
-        return topPosition > disallowedAreaForEndBoundsHeight;
-    }
-
-    /**
-     * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
-     * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
-     * valid drag area.
-     */
-    static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
-            PointF repositionStartPoint, float x, float y, Rect validDragArea) {
-        updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
-                x, y);
-        snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
-    }
-
-    private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+    public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
         // If we were never supplied a valid drag area, do not restrict movement.
         // Otherwise, we restrict deltas to keep task position inside the Rect.
-        if (validDragArea.width() == 0) return;
+        if (validDragArea.width() == 0) return false;
+        boolean result = false;
         if (repositionTaskBounds.left < validDragArea.left) {
             repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+            result = true;
         } else if (repositionTaskBounds.left > validDragArea.right) {
             repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+            result = true;
         }
         if (repositionTaskBounds.top < validDragArea.top) {
             repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+            result = true;
         } else if (repositionTaskBounds.top > validDragArea.bottom) {
             repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
+            result = true;
         }
+        return result;
     }
 
     private static float getMinWidth(DisplayController displayController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 6bfc7cd..6f8b3d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -60,9 +60,6 @@
     private final Rect mTaskBoundsAtDragStart = new Rect();
     private final PointF mRepositionStartPoint = new PointF();
     private final Rect mRepositionTaskBounds = new Rect();
-    // If a task move (not resize) finishes with the positions y less than this value, do not
-    // finalize the bounds there using WCT#setBounds
-    private final int mDisallowedAreaForEndBoundsHeight;
     private boolean mHasDragResized;
     private boolean mIsResizingOrAnimatingResize;
     private int mCtrlType;
@@ -70,11 +67,9 @@
     @Surface.Rotation private int mRotation;
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions,
-            WindowDecoration windowDecoration, DisplayController displayController,
-            int disallowedAreaForEndBoundsHeight) {
+            WindowDecoration windowDecoration, DisplayController displayController) {
         this(taskOrganizer, transitions, windowDecoration, displayController,
-                dragStartListener -> {}, SurfaceControl.Transaction::new,
-                disallowedAreaForEndBoundsHeight);
+                dragStartListener -> {}, SurfaceControl.Transaction::new);
     }
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
@@ -82,15 +77,13 @@
             WindowDecoration windowDecoration,
             DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Supplier<SurfaceControl.Transaction> supplier,
-            int disallowedAreaForEndBoundsHeight) {
+            Supplier<SurfaceControl.Transaction> supplier) {
         mTaskOrganizer = taskOrganizer;
         mTransitions = transitions;
         mWindowDecoration = windowDecoration;
         mDisplayController = displayController;
         mDragStartListener = dragStartListener;
         mTransactionSupplier = supplier;
-        mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
     }
 
     @Override
@@ -157,14 +150,10 @@
                 wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
             }
             mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
-        } else if (mCtrlType == CTRL_TYPE_UNDEFINED
-                && DragPositioningCallbackUtility.isBelowDisallowedArea(
-                mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
-                y)) {
+        } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
-            DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
-                    mWindowDecoration.calculateValidDragArea());
+            DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
             wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
             mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 5c69d55..c12a93e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -54,9 +54,6 @@
     private final Rect mTaskBoundsAtDragStart = new Rect();
     private final PointF mRepositionStartPoint = new PointF();
     private final Rect mRepositionTaskBounds = new Rect();
-    // If a task move (not resize) finishes with the positions y less than this value, do not
-    // finalize the bounds there using WCT#setBounds
-    private final int mDisallowedAreaForEndBoundsHeight;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
     private int mCtrlType;
     private boolean mIsResizingOrAnimatingResize;
@@ -66,25 +63,22 @@
             DesktopModeWindowDecoration windowDecoration,
             DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Transitions transitions,
-            int disallowedAreaForEndBoundsHeight) {
+            Transitions transitions) {
         this(taskOrganizer, windowDecoration, displayController, dragStartListener,
-                SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
+                SurfaceControl.Transaction::new, transitions);
     }
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration,
             DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
-            int disallowedAreaForEndBoundsHeight) {
+            Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
         mDesktopWindowDecoration = windowDecoration;
         mTaskOrganizer = taskOrganizer;
         mDisplayController = displayController;
         mDragStartListener = dragStartListener;
         mTransactionSupplier = supplier;
         mTransitions = transitions;
-        mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
     }
 
     @Override
@@ -151,13 +145,10 @@
                 // won't be called.
                 resetVeilIfVisible();
             }
-        } else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
-                mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
-                y)) {
+        } else {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
-            DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
-                    mDesktopWindowDecoration.calculateValidDragArea());
+            DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
             wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
             mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 9703dce..bd39aa6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -68,17 +68,17 @@
         )
         var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
             WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
-        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
         testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
             WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(
             DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
             -50,
             DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
-            2 * STABLE_INSETS.top))
+            transitionHeight))
         testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
             WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
-        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 5df9dd3..2a147b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -23,9 +23,13 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
 import android.os.Binder
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
+import android.view.SurfaceControl
 import android.view.WindowManager
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_OPEN
@@ -48,6 +52,7 @@
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.common.LaunchAdjacentController
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.ShellExecutor
@@ -86,6 +91,7 @@
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.kotlin.times
 import org.mockito.Mockito.`when` as whenever
@@ -844,6 +850,31 @@
                 .isEqualTo(WINDOWING_MODE_FULLSCREEN)
     }
 
+    @Test
+    fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
+        val task = setUpFreeformTask()
+        val mockSurface = mock(SurfaceControl::class.java)
+        val mockDisplayLayout = mock(DisplayLayout::class.java)
+        whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+        whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+        controller.onDragPositioningMove(task, mockSurface, 200f,
+            Rect(100, -100, 500, 1000))
+
+        controller.onDragPositioningEnd(task,
+            Point(100, -100), /* position */
+            PointF(200f, -200f), /* inputCoordinate */
+            Rect(100, -100, 500, 1000), /* taskBounds */
+            Rect(0, 50, 2000, 2000) /* validDragArea */
+        )
+        val rectAfterEnd = Rect(100, 50, 500, 1150)
+        verify(transitions).startTransition(
+            eq(TRANSIT_CHANGE), Mockito.argThat { wct ->
+                return@argThat wct.changes.any { (token, change) ->
+                    change.configuration.windowConfiguration.bounds == rectAfterEnd
+                }
+            }, eq(null))
+    }
+
     fun enterSplit_freeformTaskIsMovedToSplit() {
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index befc702..34b2eeb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -39,10 +39,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -63,6 +66,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -105,6 +109,8 @@
     @Mock private ShellExecutor mMainExecutor;
     @Mock private LaunchAdjacentController mLaunchAdjacentController;
     @Mock private DefaultMixedHandler mMixedHandler;
+    @Mock private SplitScreen.SplitInvocationListener mInvocationListener;
+    private final TestShellExecutor mTestShellExecutor = new TestShellExecutor();
     private SplitLayout mSplitLayout;
     private MainStage mMainStage;
     private SideStage mSideStage;
@@ -147,6 +153,7 @@
                 .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
         doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager();
         doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager();
+        mStageCoordinator.registerSplitAnimationListener(mInvocationListener, mTestShellExecutor);
     }
 
     @Test
@@ -452,6 +459,15 @@
         mMainStage.activate(new WindowContainerTransaction(), true /* includingTopTask */);
     }
 
+    @Test
+    @UiThreadTest
+    public void testSplitInvocationCallback() {
+        enterSplit();
+        mTestShellExecutor.flushAll();
+        verify(mInvocationListener, times(1))
+                .onSplitAnimationInvoked(eq(true));
+    }
+
     private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
         for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
             WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index e60be71..e6fabcf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -189,8 +189,9 @@
             DISPLAY_BOUNDS.right - 100,
             DISPLAY_BOUNDS.bottom - 100)
 
-        DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
-            startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+        DragPositioningCallbackUtility.updateTaskBounds(repositionTaskBounds, STARTING_BOUNDS,
+            startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat())
+        DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(repositionTaskBounds,
             validDragArea)
         assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
         assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index de6903d..ce7b633 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -125,8 +125,7 @@
                 mockWindowDecoration,
                 mockDisplayController,
                 mockDragStartListener,
-                mockTransactionFactory,
-                DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+                mockTransactionFactory
         )
     }
 
@@ -576,31 +575,6 @@
         })
     }
 
-    @Test
-    fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_UNDEFINED, // drag
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        val newX = STARTING_BOUNDS.right.toFloat() + 5
-        val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-
-        taskPositioner.onDragPositioningEnd(newX, newY)
-
-        verify(mockTransitions, never()).startTransition(
-                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
-            }}, eq(taskPositioner))
-    }
-
     private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
         return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
                 bounds == configuration.windowConfiguration.bounds
@@ -656,70 +630,6 @@
     }
 
     @Test
-    fun testDragResize_drag_taskPositionedInStableBounds() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_UNDEFINED, // drag
-                STARTING_BOUNDS.left.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        val newX = STARTING_BOUNDS.left.toFloat()
-        val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
-        taskPositioner.onDragPositioningEnd(
-                newX,
-                newY
-        )
-        // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
-        // but not in disallowed end bounds area.
-        verify(mockTransitions).startTransition(
-                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
-                        change.configuration.windowConfiguration.bounds.top ==
-                        STABLE_BOUNDS_LANDSCAPE.top
-            }}, eq(taskPositioner))
-    }
-
-    @Test
-    fun testDragResize_drag_taskPositionedInValidDragArea() {
-        taskPositioner.onDragPositioningStart(
-            CTRL_TYPE_UNDEFINED, // drag
-            STARTING_BOUNDS.left.toFloat(),
-            STARTING_BOUNDS.top.toFloat()
-        )
-
-        val newX = VALID_DRAG_AREA.left - 500f
-        val newY = VALID_DRAG_AREA.bottom + 500f
-        taskPositioner.onDragPositioningMove(
-            newX,
-            newY
-        )
-        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
-        taskPositioner.onDragPositioningEnd(
-            newX,
-            newY
-        )
-        verify(mockTransitions).startTransition(
-                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
-                        change.configuration.windowConfiguration.bounds.top ==
-                        VALID_DRAG_AREA.bottom &&
-                        change.configuration.windowConfiguration.bounds.left ==
-                        VALID_DRAG_AREA.left
-            }}, eq(taskPositioner))
-    }
-
-    @Test
     fun testDragResize_drag_updatesStableBoundsOnRotate() {
         // Test landscape stable bounds
         performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 86253f3..7f6e538 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -138,8 +138,7 @@
                         mockDisplayController,
                         mockDragStartListener,
                         mockTransactionFactory,
-                        mockTransitions,
-                        DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+                        mockTransitions
                 )
     }
 
@@ -355,68 +354,6 @@
     }
 
     @Test
-    fun testDragResize_drag_taskPositionedInStableBounds() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_UNDEFINED, // drag
-                STARTING_BOUNDS.left.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        val newX = STARTING_BOUNDS.left.toFloat()
-        val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
-        taskPositioner.onDragPositioningEnd(
-                newX,
-                newY
-        )
-        // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
-        // but not in disallowed end bounds area.
-        verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
-                        change.configuration.windowConfiguration.bounds.top ==
-                        STABLE_BOUNDS_LANDSCAPE.top }},
-                eq(taskPositioner))
-    }
-
-    @Test
-    fun testDragResize_drag_taskPositionedInValidDragArea() {
-        taskPositioner.onDragPositioningStart(
-            CTRL_TYPE_UNDEFINED, // drag
-            STARTING_BOUNDS.left.toFloat(),
-            STARTING_BOUNDS.top.toFloat()
-        )
-
-        val newX = VALID_DRAG_AREA.left - 500f
-        val newY = VALID_DRAG_AREA.bottom + 500f
-        taskPositioner.onDragPositioningMove(
-            newX,
-            newY
-        )
-        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
-        taskPositioner.onDragPositioningEnd(
-            newX,
-            newY
-        )
-        verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
-                        change.configuration.windowConfiguration.bounds.top ==
-                        VALID_DRAG_AREA.bottom &&
-                        change.configuration.windowConfiguration.bounds.left ==
-                        VALID_DRAG_AREA.left }},
-                eq(taskPositioner))
-    }
-
-    @Test
     fun testDragResize_drag_updatesStableBoundsOnRotate() {
         // Test landscape stable bounds
         performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/libs/hwui/Mesh.cpp b/libs/hwui/Mesh.cpp
index 37a7d74..5ef7acd 100644
--- a/libs/hwui/Mesh.cpp
+++ b/libs/hwui/Mesh.cpp
@@ -21,6 +21,8 @@
 
 #include "SafeMath.h"
 
+namespace android {
+
 static size_t min_vcount_for_mode(SkMesh::Mode mode) {
     switch (mode) {
         case SkMesh::Mode::kTriangles:
@@ -28,6 +30,7 @@
         case SkMesh::Mode::kTriangleStrip:
             return 3;
     }
+    return 1;
 }
 
 // Re-implementation of SkMesh::validate to validate user side that their mesh is valid.
@@ -36,29 +39,30 @@
     if (!mMeshSpec) {
         FAIL_MESH_VALIDATE("MeshSpecification is required.");
     }
-    if (mVertexBufferData.empty()) {
+    if (mBufferData->vertexData().empty()) {
         FAIL_MESH_VALIDATE("VertexBuffer is required.");
     }
 
-    auto meshStride = mMeshSpec->stride();
-    auto meshMode = SkMesh::Mode(mMode);
+    size_t vertexStride = mMeshSpec->stride();
+    size_t vertexCount = mBufferData->vertexCount();
+    size_t vertexOffset = mBufferData->vertexOffset();
     SafeMath sm;
-    size_t vsize = sm.mul(meshStride, mVertexCount);
-    if (sm.add(vsize, mVertexOffset) > mVertexBufferData.size()) {
+    size_t vertexSize = sm.mul(vertexStride, vertexCount);
+    if (sm.add(vertexSize, vertexOffset) > mBufferData->vertexData().size()) {
         FAIL_MESH_VALIDATE(
                 "The vertex buffer offset and vertex count reads beyond the end of the"
                 " vertex buffer.");
     }
 
-    if (mVertexOffset % meshStride != 0) {
+    if (vertexOffset % vertexStride != 0) {
         FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).",
-                           mVertexOffset, meshStride);
+                           vertexOffset, vertexStride);
     }
 
     if (size_t uniformSize = mMeshSpec->uniformSize()) {
-        if (!mBuilder->fUniforms || mBuilder->fUniforms->size() < uniformSize) {
+        if (!mUniformBuilder.fUniforms || mUniformBuilder.fUniforms->size() < uniformSize) {
             FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.",
-                               mBuilder->fUniforms->size(), uniformSize);
+                               mUniformBuilder.fUniforms->size(), uniformSize);
         }
     }
 
@@ -69,29 +73,33 @@
             case SkMesh::Mode::kTriangleStrip:
                 return "triangle-strip";
         }
+        return "unknown";
     };
-    if (!mIndexBufferData.empty()) {
-        if (mIndexCount < min_vcount_for_mode(meshMode)) {
+
+    size_t indexCount = mBufferData->indexCount();
+    size_t indexOffset = mBufferData->indexOffset();
+    if (!mBufferData->indexData().empty()) {
+        if (indexCount < min_vcount_for_mode(mMode)) {
             FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.",
-                               modeToStr(meshMode), min_vcount_for_mode(meshMode), mIndexCount);
+                               modeToStr(mMode), min_vcount_for_mode(mMode), indexCount);
         }
-        size_t isize = sm.mul(sizeof(uint16_t), mIndexCount);
-        if (sm.add(isize, mIndexOffset) > mIndexBufferData.size()) {
+        size_t isize = sm.mul(sizeof(uint16_t), indexCount);
+        if (sm.add(isize, indexOffset) > mBufferData->indexData().size()) {
             FAIL_MESH_VALIDATE(
                     "The index buffer offset and index count reads beyond the end of the"
                     " index buffer.");
         }
         // If we allow 32 bit indices then this should enforce 4 byte alignment in that case.
-        if (!SkIsAlign2(mIndexOffset)) {
+        if (!SkIsAlign2(indexOffset)) {
             FAIL_MESH_VALIDATE("The index offset must be a multiple of 2.");
         }
     } else {
-        if (mVertexCount < min_vcount_for_mode(meshMode)) {
+        if (vertexCount < min_vcount_for_mode(mMode)) {
             FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.",
-                               modeToStr(meshMode), min_vcount_for_mode(meshMode), mVertexCount);
+                               modeToStr(mMode), min_vcount_for_mode(mMode), vertexCount);
         }
-        LOG_ALWAYS_FATAL_IF(mIndexCount != 0);
-        LOG_ALWAYS_FATAL_IF(mIndexOffset != 0);
+        LOG_ALWAYS_FATAL_IF(indexCount != 0);
+        LOG_ALWAYS_FATAL_IF(indexOffset != 0);
     }
 
     if (!sm.ok()) {
@@ -100,3 +108,5 @@
 #undef FAIL_MESH_VALIDATE
     return {true, {}};
 }
+
+}  // namespace android
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 69fda34..8c6ca97 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -25,6 +25,8 @@
 
 #include <utility>
 
+namespace android {
+
 class MeshUniformBuilder {
 public:
     struct MeshUniform {
@@ -103,32 +105,146 @@
     sk_sp<SkMeshSpecification> fMeshSpec;
 };
 
-class Mesh {
+// Storage for CPU and GPU copies of the vertex and index data of a mesh.
+class MeshBufferData {
 public:
-    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
-         std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
-         std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
-            : mMeshSpec(meshSpec)
-            , mMode(mode)
-            , mVertexBufferData(std::move(vertexBufferData))
-            , mVertexCount(vertexCount)
+    MeshBufferData(std::vector<uint8_t> vertexData, int32_t vertexCount, int32_t vertexOffset,
+                   std::vector<uint8_t> indexData, int32_t indexCount, int32_t indexOffset)
+            : mVertexCount(vertexCount)
             , mVertexOffset(vertexOffset)
-            , mBuilder(std::move(builder))
-            , mBounds(bounds) {}
-
-    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
-         std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
-         std::vector<uint8_t>&& indexBuffer, jint indexCount, jint indexOffset,
-         std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
-            : mMeshSpec(meshSpec)
-            , mMode(mode)
-            , mVertexBufferData(std::move(vertexBufferData))
-            , mVertexCount(vertexCount)
-            , mVertexOffset(vertexOffset)
-            , mIndexBufferData(std::move(indexBuffer))
             , mIndexCount(indexCount)
             , mIndexOffset(indexOffset)
-            , mBuilder(std::move(builder))
+            , mVertexData(std::move(vertexData))
+            , mIndexData(std::move(indexData)) {}
+
+    void updateBuffers(GrDirectContext* context) const {
+        GrDirectContext::DirectContextID currentId = context == nullptr
+                                                             ? GrDirectContext::DirectContextID()
+                                                             : context->directContextID();
+        if (currentId == mSkiaBuffers.fGenerationId && mSkiaBuffers.fVertexBuffer != nullptr) {
+            // Nothing to update since the Android API does not support partial updates yet.
+            return;
+        }
+
+        mSkiaBuffers.fVertexBuffer =
+#ifdef __ANDROID__
+                SkMeshes::MakeVertexBuffer(context, mVertexData.data(), mVertexData.size());
+#else
+                SkMeshes::MakeVertexBuffer(mVertexData.data(), mVertexData.size());
+#endif
+        if (mIndexCount != 0) {
+            mSkiaBuffers.fIndexBuffer =
+#ifdef __ANDROID__
+                    SkMeshes::MakeIndexBuffer(context, mIndexData.data(), mIndexData.size());
+#else
+                    SkMeshes::MakeIndexBuffer(mIndexData.data(), mIndexData.size());
+#endif
+        }
+        mSkiaBuffers.fGenerationId = currentId;
+    }
+
+    SkMesh::VertexBuffer* vertexBuffer() const { return mSkiaBuffers.fVertexBuffer.get(); }
+
+    sk_sp<SkMesh::VertexBuffer> refVertexBuffer() const { return mSkiaBuffers.fVertexBuffer; }
+    int32_t vertexCount() const { return mVertexCount; }
+    int32_t vertexOffset() const { return mVertexOffset; }
+
+    sk_sp<SkMesh::IndexBuffer> refIndexBuffer() const { return mSkiaBuffers.fIndexBuffer; }
+    int32_t indexCount() const { return mIndexCount; }
+    int32_t indexOffset() const { return mIndexOffset; }
+
+    const std::vector<uint8_t>& vertexData() const { return mVertexData; }
+    const std::vector<uint8_t>& indexData() const { return mIndexData; }
+
+private:
+    struct CachedSkiaBuffers {
+        sk_sp<SkMesh::VertexBuffer> fVertexBuffer;
+        sk_sp<SkMesh::IndexBuffer> fIndexBuffer;
+        GrDirectContext::DirectContextID fGenerationId = GrDirectContext::DirectContextID();
+    };
+
+    mutable CachedSkiaBuffers mSkiaBuffers;
+    int32_t mVertexCount = 0;
+    int32_t mVertexOffset = 0;
+    int32_t mIndexCount = 0;
+    int32_t mIndexOffset = 0;
+    std::vector<uint8_t> mVertexData;
+    std::vector<uint8_t> mIndexData;
+};
+
+class Mesh {
+public:
+    // A snapshot of the mesh for use by the render thread.
+    //
+    // After a snapshot is taken, future uniform changes to the original Mesh will not modify the
+    // uniforms returned by makeSkMesh.
+    class Snapshot {
+    public:
+        Snapshot() = delete;
+        Snapshot(const Snapshot&) = default;
+        Snapshot(Snapshot&&) = default;
+        Snapshot& operator=(const Snapshot&) = default;
+        Snapshot& operator=(Snapshot&&) = default;
+        ~Snapshot() = default;
+
+        const SkMesh& getSkMesh() const {
+            SkMesh::VertexBuffer* vertexBuffer = mBufferData->vertexBuffer();
+            LOG_FATAL_IF(vertexBuffer == nullptr,
+                         "Attempt to obtain SkMesh when vertexBuffer has not been created, did you "
+                         "forget to call MeshBufferData::updateBuffers with a GrDirectContext?");
+            if (vertexBuffer != mMesh.vertexBuffer()) mMesh = makeSkMesh();
+            return mMesh;
+        }
+
+    private:
+        friend class Mesh;
+
+        Snapshot(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode,
+                 std::shared_ptr<const MeshBufferData> bufferData, sk_sp<const SkData> uniforms,
+                 const SkRect& bounds)
+                : mMeshSpec(std::move(meshSpec))
+                , mMode(mode)
+                , mBufferData(std::move(bufferData))
+                , mUniforms(std::move(uniforms))
+                , mBounds(bounds) {}
+
+        SkMesh makeSkMesh() const {
+            const MeshBufferData& d = *mBufferData;
+            if (d.indexCount() != 0) {
+                return SkMesh::MakeIndexed(mMeshSpec, mMode, d.refVertexBuffer(), d.vertexCount(),
+                                           d.vertexOffset(), d.refIndexBuffer(), d.indexCount(),
+                                           d.indexOffset(), mUniforms,
+                                           SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
+                        .mesh;
+            }
+            return SkMesh::Make(mMeshSpec, mMode, d.refVertexBuffer(), d.vertexCount(),
+                                d.vertexOffset(), mUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
+                                mBounds)
+                    .mesh;
+        }
+
+        mutable SkMesh mMesh;
+        sk_sp<SkMeshSpecification> mMeshSpec;
+        SkMesh::Mode mMode;
+        std::shared_ptr<const MeshBufferData> mBufferData;
+        sk_sp<const SkData> mUniforms;
+        SkRect mBounds;
+    };
+
+    Mesh(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode, std::vector<uint8_t> vertexData,
+         int32_t vertexCount, int32_t vertexOffset, const SkRect& bounds)
+            : Mesh(std::move(meshSpec), mode, std::move(vertexData), vertexCount, vertexOffset,
+                   /* indexData = */ {}, /* indexCount = */ 0, /* indexOffset = */ 0, bounds) {}
+
+    Mesh(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode, std::vector<uint8_t> vertexData,
+         int32_t vertexCount, int32_t vertexOffset, std::vector<uint8_t> indexData,
+         int32_t indexCount, int32_t indexOffset, const SkRect& bounds)
+            : mMeshSpec(std::move(meshSpec))
+            , mMode(mode)
+            , mBufferData(std::make_shared<MeshBufferData>(std::move(vertexData), vertexCount,
+                                                           vertexOffset, std::move(indexData),
+                                                           indexCount, indexOffset))
+            , mUniformBuilder(mMeshSpec)
             , mBounds(bounds) {}
 
     Mesh(Mesh&&) = default;
@@ -137,77 +253,22 @@
 
     [[nodiscard]] std::tuple<bool, SkString> validate();
 
-    void updateSkMesh(GrDirectContext* context) const {
-        GrDirectContext::DirectContextID genId = GrDirectContext::DirectContextID();
-        if (context) {
-            genId = context->directContextID();
-        }
+    std::shared_ptr<const MeshBufferData> refBufferData() const { return mBufferData; }
 
-        if (mIsDirty || genId != mGenerationId) {
-            auto vertexData = reinterpret_cast<const void*>(mVertexBufferData.data());
-#ifdef __ANDROID__
-            auto vb = SkMeshes::MakeVertexBuffer(context,
-                                                 vertexData,
-                                                 mVertexBufferData.size());
-#else
-            auto vb = SkMeshes::MakeVertexBuffer(vertexData,
-                                                 mVertexBufferData.size());
-#endif
-            auto meshMode = SkMesh::Mode(mMode);
-            if (!mIndexBufferData.empty()) {
-                auto indexData = reinterpret_cast<const void*>(mIndexBufferData.data());
-#ifdef __ANDROID__
-                auto ib = SkMeshes::MakeIndexBuffer(context,
-                                                    indexData,
-                                                    mIndexBufferData.size());
-#else
-                auto ib = SkMeshes::MakeIndexBuffer(indexData,
-                                                    mIndexBufferData.size());
-#endif
-                mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
-                                            ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
-                                            SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
-                                .mesh;
-            } else {
-                mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
-                                     mBuilder->fUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
-                                     mBounds)
-                                .mesh;
-            }
-            mIsDirty = false;
-            mGenerationId = genId;
-        }
+    Snapshot takeSnapshot() const {
+        return Snapshot(mMeshSpec, mMode, mBufferData, mUniformBuilder.fUniforms, mBounds);
     }
 
-    SkMesh& getSkMesh() const {
-        LOG_FATAL_IF(mIsDirty,
-                     "Attempt to obtain SkMesh when Mesh is dirty, did you "
-                     "forget to call updateSkMesh with a GrDirectContext? "
-                     "Defensively creating a CPU mesh");
-        return mMesh;
-    }
-
-    void markDirty() { mIsDirty = true; }
-
-    MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }
+    MeshUniformBuilder* uniformBuilder() { return &mUniformBuilder; }
 
 private:
     sk_sp<SkMeshSpecification> mMeshSpec;
-    int mMode = 0;
-
-    std::vector<uint8_t> mVertexBufferData;
-    size_t mVertexCount = 0;
-    size_t mVertexOffset = 0;
-
-    std::vector<uint8_t> mIndexBufferData;
-    size_t mIndexCount = 0;
-    size_t mIndexOffset = 0;
-
-    std::unique_ptr<MeshUniformBuilder> mBuilder;
-    SkRect mBounds{};
-
-    mutable SkMesh mMesh{};
-    mutable bool mIsDirty = true;
-    mutable GrDirectContext::DirectContextID mGenerationId = GrDirectContext::DirectContextID();
+    SkMesh::Mode mMode;
+    std::shared_ptr<MeshBufferData> mBufferData;
+    MeshUniformBuilder mUniformBuilder;
+    SkRect mBounds;
 };
+
+}  // namespace android
+
 #endif  // MESH_H_
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 54aef55..d026379 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -573,9 +573,9 @@
 struct DrawMesh final : Op {
     static const auto kType = Type::DrawMesh;
     DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
-            : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+            : mesh(mesh.takeSnapshot()), blender(std::move(blender)), paint(paint) {}
 
-    const Mesh& mesh;
+    Mesh::Snapshot mesh;
     sk_sp<SkBlender> blender;
     SkPaint paint;
 
@@ -1296,14 +1296,5 @@
     fDL->drawWebView(drawable);
 }
 
-[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const {
-    LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null");
-    if (meshWrapper) {
-        return meshWrapper->getSkMesh();
-    } else {
-        return *mesh;
-    }
-}
-
 }  // namespace uirenderer
 }  // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 965264f..f867852 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -41,11 +41,12 @@
 
 enum class SkBlendMode;
 class SkRRect;
-class Mesh;
 
 namespace android {
-namespace uirenderer {
 
+class Mesh;
+
+namespace uirenderer {
 namespace skiapipeline {
 class FunctorDrawable;
 }
@@ -68,18 +69,6 @@
 
 static_assert(sizeof(DisplayListOp) == 4);
 
-class DrawMeshPayload {
-public:
-    explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {}
-    explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {}
-
-    [[nodiscard]] const SkMesh& getSkMesh() const;
-
-private:
-    const SkMesh* mesh = nullptr;
-    const Mesh* meshWrapper = nullptr;
-};
-
 struct DrawImagePayload {
     explicit DrawImagePayload(Bitmap& bitmap)
             : image(bitmap.makeImage()), palette(bitmap.palette()) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 0b739c3..72e83af 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -596,8 +596,8 @@
     if (recordingContext) {
         context = recordingContext->asDirectContext();
     }
-    mesh.updateSkMesh(context);
-    mCanvas->drawMesh(mesh.getSkMesh(), blender, paint);
+    mesh.refBufferData()->updateBuffers(context);
+    mCanvas->drawMesh(mesh.takeSnapshot().getSkMesh(), blender, paint);
 }
 
 // ----------------------------------------------------------------------------
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 14b4f58..4eb6918 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -34,7 +34,6 @@
 class SkRRect;
 class SkRuntimeShaderBuilder;
 class SkVertices;
-class Mesh;
 
 namespace minikin {
 class Font;
@@ -61,6 +60,7 @@
 
 class AnimatedImageDrawable;
 class Bitmap;
+class Mesh;
 class Paint;
 struct Typeface;
 
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
index 5cb43e5..3109de5 100644
--- a/libs/hwui/jni/android_graphics_Mesh.cpp
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -38,8 +38,8 @@
         return 0;
     }
     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto meshPtr = new Mesh(skMeshSpec, mode, std::move(buffer), vertexCount, vertexOffset,
-                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+    auto meshPtr = new Mesh(skMeshSpec, static_cast<SkMesh::Mode>(mode), std::move(buffer),
+                            vertexCount, vertexOffset, skRect);
     auto [valid, msg] = meshPtr->validate();
     if (!valid) {
         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
@@ -63,9 +63,9 @@
         return 0;
     }
     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto meshPtr = new Mesh(skMeshSpec, mode, std::move(vBuf), vertexCount, vertexOffset,
-                            std::move(iBuf), indexCount, indexOffset,
-                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+    auto meshPtr =
+            new Mesh(skMeshSpec, static_cast<SkMesh::Mode>(mode), std::move(vBuf), vertexCount,
+                     vertexOffset, std::move(iBuf), indexCount, indexOffset, skRect);
     auto [valid, msg] = meshPtr->validate();
     if (!valid) {
         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
@@ -133,7 +133,6 @@
     ScopedUtfChars name(env, uniformName);
     const float values[4] = {value1, value2, value3, value4};
     nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
-    wrapper->markDirty();
 }
 
 static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
@@ -143,7 +142,6 @@
     AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
     nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
                               autoValues.length(), isColor);
-    wrapper->markDirty();
 }
 
 static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
@@ -166,7 +164,6 @@
     ScopedUtfChars name(env, uniformName);
     const int values[4] = {value1, value2, value3, value4};
     nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
-    wrapper->markDirty();
 }
 
 static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
@@ -176,7 +173,6 @@
     AutoJavaIntArray autoValues(env, values, 0);
     nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
                             autoValues.length());
-    wrapper->markDirty();
 }
 
 static void MeshWrapper_destroy(Mesh* wrapper) {
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 6ccb212..40dfc9d 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <FileBlobCache.h>
 #include <GrContextOptions.h>
 #include <SkRefCnt.h>
 #include <cutils/compiler.h>
@@ -32,7 +33,6 @@
 namespace android {
 
 class BlobCache;
-class FileBlobCache;
 
 namespace uirenderer {
 namespace skiapipeline {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 5c8285a..e0216b6 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -111,8 +111,8 @@
     }
 
     auto grContext = info.canvasContext.getGrContext();
-    for (auto mesh : mMeshes) {
-        mesh->updateSkMesh(grContext);
+    for (const auto& bufferData : mMeshBufferData) {
+        bufferData->updateBuffers(grContext);
     }
 
 #endif
@@ -181,7 +181,7 @@
 
     mDisplayList.reset();
 
-    mMeshes.clear();
+    mMeshBufferData.clear();
     mMutableImages.clear();
     mVectorDrawables.clear();
     mAnimatedImages.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index b9dc1c4..071a4e8 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <deque>
+#include <memory>
 
 #include "Mesh.h"
 #include "RecordingCanvas.h"
@@ -172,7 +173,7 @@
     std::deque<RenderNodeDrawable> mChildNodes;
     std::deque<FunctorDrawable*> mChildFunctors;
     std::vector<SkImage*> mMutableImages;
-    std::vector<const Mesh*> mMeshes;
+    std::vector<std::shared_ptr<const MeshBufferData>> mMeshBufferData;
 
 private:
     std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index e917f9a..45bfe1c 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -342,7 +342,7 @@
 }
 
 void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
-    mDisplayList->mMeshes.push_back(&mesh);
+    mDisplayList->mMeshBufferData.push_back(mesh.refBufferData());
     mRecorder.drawMesh(mesh, blender, paint);
 }
 
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 4be282b..4217562 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1690,6 +1690,18 @@
     }
 
     /**
+     * Query if the usage is a hidden (neither sdk nor SystemApi) usage
+     *
+     * @param usage the {@link android.media.AudioAttributes usage}
+     * @return {@code true} if the usage is {@link AudioAttributes#USAGE_VIRTUAL_SOURCE} or
+     *     {@code false} otherwise
+     * @hide
+     */
+    public static boolean isHiddenUsage(@AttributeUsage int usage) {
+        return usage == USAGE_VIRTUAL_SOURCE;
+    }
+
+    /**
      * Query if the content type is a valid sdk content type
      * @param contentType one of {@link AttributeContentType}
      * @return {@code true} if the content type is valid for sdk or {@code false} otherwise
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index e6ec2c3..eaafa59 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -694,7 +694,8 @@
     }
 
     private static boolean isUsageValid(int usage) {
-        return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage);
+        return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage)
+                || AudioAttributes.isHiddenUsage(usage);
     }
 
     private void ensureFadingIsEnabled() {
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index 236b1fd..c48a956 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -45,10 +45,8 @@
 @RunWith(AndroidJUnit4.class)
 @RequiresFlagsEnabled(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
 public final class FadeManagerConfigurationUnitTest {
-    private static final long DEFAULT_FADE_OUT_DURATION_MS =
-            FadeManagerConfiguration.getDefaultFadeOutDurationMillis();
-    private static final long DEFAULT_FADE_IN_DURATION_MS =
-            FadeManagerConfiguration.getDefaultFadeInDurationMillis();
+    private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+    private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
     private static final long TEST_FADE_OUT_DURATION_MS = 1_500;
     private static final long TEST_FADE_IN_DURATION_MS = 750;
     private static final int TEST_INVALID_USAGE = -10;
@@ -251,6 +249,20 @@
     }
 
     @Test
+    public void testGetDefaultFadeOutDuration() {
+        expect.withMessage("Default fade out duration")
+                .that(FadeManagerConfiguration.getDefaultFadeOutDurationMillis())
+                .isEqualTo(DEFAULT_FADE_OUT_DURATION_MS);
+    }
+
+    @Test
+    public void testGetDefaultFadeInDuration() {
+        expect.withMessage("Default fade in duration")
+                .that(FadeManagerConfiguration.getDefaultFadeInDurationMillis())
+                .isEqualTo(DEFAULT_FADE_IN_DURATION_MS);
+    }
+
+    @Test
     public void testSetFadeState_toDisable() {
         final int fadeState = FadeManagerConfiguration.FADE_STATE_DISABLED;
         FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
@@ -746,6 +758,87 @@
                 .isEqualTo(FadeManagerConfiguration.CREATOR.createFromParcel(parcel));
     }
 
+    @Test
+    public void testGetFadeOutVolumeShaperConfigForUsage_withInvalidUsage_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                mFmc.getFadeOutVolumeShaperConfigForUsage(TEST_INVALID_USAGE)
+        );
+
+        expect.withMessage("Fade out volume shaper config for invalid usage exception")
+                .that(thrown).hasMessageThat().contains("Invalid usage");
+    }
+
+    @Test
+    public void testGetFadeInVolumeShaperConfigForUsage_withInvalidUsage_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                mFmc.getFadeInVolumeShaperConfigForUsage(TEST_INVALID_USAGE)
+        );
+
+        expect.withMessage("Fade in volume shaper config for invalid usage exception")
+                .that(thrown).hasMessageThat().contains("Invalid usage");
+    }
+
+    @Test
+    public void testGetFadeVolumeShaperConfigForUsage_forSdkUsages() {
+        FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder();
+        for (int usage : AudioAttributes.getSdkUsages()) {
+            builder.addFadeableUsage(usage);
+            builder.setFadeOutVolumeShaperConfigForUsage(usage, TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+            builder.setFadeInVolumeShaperConfigForUsage(usage, TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+        }
+        FadeManagerConfiguration fmc = builder.build();
+
+        for (int usage : AudioAttributes.getSdkUsages()) {
+            expect.withMessage("Fade out volume shaper config for sdk usage")
+                    .that(fmc.getFadeOutVolumeShaperConfigForUsage(usage))
+                    .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+            expect.withMessage("Fade in volume shaper config for sdk usage")
+                    .that(fmc.getFadeInVolumeShaperConfigForUsage(usage))
+                    .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+        }
+    }
+
+    @Test
+    public void testGetFadeVolumeShaperConfigForUsage_forSystemUsages() {
+        int[] systemUsages = {AudioAttributes.USAGE_CALL_ASSISTANT, AudioAttributes.USAGE_EMERGENCY,
+                AudioAttributes.USAGE_SAFETY, AudioAttributes.USAGE_VEHICLE_STATUS,
+                AudioAttributes.USAGE_ANNOUNCEMENT};
+        FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder();
+        for (int usage : systemUsages) {
+            builder.addFadeableUsage(usage);
+            builder.setFadeOutVolumeShaperConfigForUsage(usage, TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+            builder.setFadeInVolumeShaperConfigForUsage(usage, TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+        }
+        FadeManagerConfiguration fmc = builder.build();
+
+        for (int usage : systemUsages) {
+            expect.withMessage("Fade out volume shaper config for system usage")
+                    .that(fmc.getFadeOutVolumeShaperConfigForUsage(usage))
+                    .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+            expect.withMessage("Fade in volume shaper config for system usage")
+                    .that(fmc.getFadeInVolumeShaperConfigForUsage(usage))
+                    .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+        }
+    }
+
+    @Test
+    public void testGetFadeVolumeShaperConfigForUsage_forHiddenUsage() {
+        int hiddenUsage = AudioAttributes.USAGE_VIRTUAL_SOURCE;
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .addFadeableUsage(hiddenUsage)
+                .setFadeOutVolumeShaperConfigForUsage(hiddenUsage,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForUsage(hiddenUsage,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build();
+
+        expect.withMessage("Fade out volume shaper config for hidden usage")
+                .that(fmc.getFadeOutVolumeShaperConfigForUsage(hiddenUsage))
+                .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+        expect.withMessage("Fade in volume shaper config for hidden usage")
+                .that(fmc.getFadeInVolumeShaperConfigForUsage(hiddenUsage))
+                .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+    }
+
     private static AudioAttributes createAudioAttributesForUsage(int usage) {
         if (AudioAttributes.isSystemUsage(usage)) {
             return new AudioAttributes.Builder().setSystemUsage(usage).build();
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 752ebdf..4812685 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -58,6 +58,7 @@
         "configuration.cpp",
         "hardware_buffer_jni.cpp",
         "input.cpp",
+        "input_transfer_token.cpp",
         "looper.cpp",
         "native_activity.cpp",
         "native_window_jni.cpp",
diff --git a/native/android/input_transfer_token.cpp b/native/android/input_transfer_token.cpp
new file mode 100644
index 0000000..501e1d3
--- /dev/null
+++ b/native/android/input_transfer_token.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "InputTransferToken"
+
+#include <android/input_transfer_token_jni.h>
+#include <android_runtime/android_window_InputTransferToken.h>
+#include <gui/InputTransferToken.h>
+#include <log/log_main.h>
+
+using namespace android;
+
+#define CHECK_NOT_NULL(name) \
+    LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument");
+
+void InputTransferToken_acquire(InputTransferToken* inputTransferToken) {
+    // incStrong/decStrong token must be the same, doesn't matter what it is
+    inputTransferToken->incStrong((void*)InputTransferToken_acquire);
+}
+
+void InputTransferToken_release(InputTransferToken* inputTransferToken) {
+    // incStrong/decStrong token must be the same, doesn't matter what it is
+    inputTransferToken->decStrong((void*)InputTransferToken_acquire);
+}
+
+AInputTransferToken* AInputTransferToken_fromJava(JNIEnv* env, jobject inputTransferTokenObj) {
+    CHECK_NOT_NULL(env);
+    CHECK_NOT_NULL(inputTransferTokenObj);
+    InputTransferToken* inputTransferToken =
+            android_window_InputTransferToken_getNativeInputTransferToken(env,
+                                                                          inputTransferTokenObj);
+    CHECK_NOT_NULL(inputTransferToken);
+    InputTransferToken_acquire(inputTransferToken);
+    return reinterpret_cast<AInputTransferToken*>(inputTransferToken);
+}
+
+jobject AInputTransferToken_toJava(JNIEnv* _Nonnull env,
+                                   const AInputTransferToken* aInputTransferToken) {
+    CHECK_NOT_NULL(env);
+    CHECK_NOT_NULL(aInputTransferToken);
+    const InputTransferToken* inputTransferToken =
+            reinterpret_cast<const InputTransferToken*>(aInputTransferToken);
+    return android_window_InputTransferToken_getJavaInputTransferToken(env, inputTransferToken);
+}
+
+void AInputTransferToken_release(AInputTransferToken* aInputTransferToken) {
+    CHECK_NOT_NULL(aInputTransferToken);
+    InputTransferToken* inputTransferToken =
+            reinterpret_cast<InputTransferToken*>(aInputTransferToken);
+    InputTransferToken_release(inputTransferToken);
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 35e37b2..b2925bf 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -98,6 +98,9 @@
     AInputQueue_getEvent;
     AInputQueue_hasEvents;
     AInputQueue_preDispatchEvent;
+    AInputTransferToken_fromJava; # introduced=35
+    AInputTransferToken_release; # introduced=35
+    AInputTransferToken_toJava; # introduced=35
     AKeyEvent_getAction;
     AKeyEvent_getDownTime;
     AKeyEvent_getEventTime;
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 7028c8f..af63a6e 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -174,6 +174,16 @@
                 && frame.getBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT);
     }
 
+    /**
+     * Constructor for Polling Frames.
+     *
+     * @param type the type of the frame
+     * @param data a byte array of the data contained in the frame
+     * @param gain the vendor-specific gain of the field
+     * @param timestamp the timestamp in millisecones
+     * @param triggeredAutoTransact whether or not this frame triggered the device to start a
+     * transaction automatically
+     */
     public PollingFrame(@PollingFrameType int type, @Nullable byte[] data,
             int gain, int timestamp, boolean triggeredAutoTransact) {
         mType = type;
diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md
new file mode 100644
index 0000000..30cb993
--- /dev/null
+++ b/packages/SettingsLib/DataStore/README.md
@@ -0,0 +1,164 @@
+# Datastore library
+
+This library aims to manage datastore in a consistent way.
+
+## Overview
+
+A datastore is required to extend the `BackupRestoreStorage` class and implement
+either `Observable` or `KeyedObservable` interface, which enforces:
+
+-   Backup and restore: Datastore should support
+    [data backup](https://developer.android.com/guide/topics/data/backup) to
+    preserve user experiences on a new device.
+-   Observer pattern: The
+    [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
+    monitor data change in the datastore and
+    -   trigger
+        [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\))
+        automatically.
+    -   track data change event to log metrics.
+    -   update internal state and take action.
+
+### Backup and restore
+
+The Android backup framework provides
+[BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper)
+and
+[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)
+to back up a datastore. However, there are several caveats when implement
+`BackupHelper`:
+
+-   performBackup: The data is updated incrementally but it is not well
+    documented. The `ParcelFileDescriptor` state parameters are normally ignored
+    and data is updated even there is no change.
+-   restoreEntity: The implementation must take care not to seek or close the
+    underlying data source, nor read more than size() bytes from the stream when
+    restore (see
+    [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)).
+    It is possible a `BackupHelper` prevents other `BackupHelper`s from
+    restoring data.
+-   writeNewStateDescription: Existing implementations rarely notice that this
+    callback is invoked after all entities are restored, and check if necessary
+    data are all restored in `restoreEntity` (e.g.
+    [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)),
+    which is not robust sometimes.
+
+This library provides more clear API and offers some improvements:
+
+-   The implementation only needs to focus on the `BackupRestoreEntity`
+    interface. The `InputStream` of restore will ensure bounded data are read,
+    and close the stream will be no-op.
+-   The library computes checksum of the backup data automatically, so that
+    unchanged data will not be sent to Android backup system.
+-   Data compression is supported:
+    -   ZIP best compression is enabled by default, no extra effort needs to be
+        taken.
+    -   It is safe to switch between compression and no compression in future,
+        the backup data will add 1 byte header to recognize the codec.
+    -   To support other compression algorithms, simply wrap over the
+        `InputStream` and `OutputStream`. Actually, the checksum is computed in
+        this way by
+        [CheckedInputStream](https://developer.android.com/reference/java/util/zip/CheckedInputStream)
+        and
+        [CheckedOutputStream](https://developer.android.com/reference/java/util/zip/CheckedOutputStream),
+        see `BackupRestoreStorage` implementation for more details.
+-   Enhanced forward compatibility for file is enabled: If a backup includes
+    data that didn't exist in earlier versions of the app, the data can still be
+    successfully restored in those older versions. This is achieved by extending
+    the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will
+    treat each file as an entity and do the backup / restore.
+-   Manual `BackupManager.dataChanged` call is unnecessary now, the library will
+    do the invocation (see next section).
+
+### Observer pattern
+
+Manual `BackupManager.dataChanged` call is required by current backup framework.
+In practice, it is found that `SharedPreferences` usages foget to invoke the
+API. Besides, there are common use cases to log metrics when data is changed.
+Consequently, observer pattern is employed to resolve the issues.
+
+If the datastore is key-value based (e.g. `SharedPreferences`), implements the
+`KeyedObservable` interface to offer fine-grained observer. Otherwise,
+implements `Observable`. The library provides thread-safe implementations
+(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be
+helpful.
+
+Keep in mind that the implementation should call `KeyedObservable.notifyChange`
+/ `Observable.notifyChange` whenever internal data is changed, so that the
+registered observer will be notified properly.
+
+## Usage and example
+
+For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To
+back up other file based storage, extend the `BackupRestoreFileStorage` class.
+
+Here is an example of customized datastore, which has a string to back up:
+
+```kotlin
+class MyDataStore : ObservableBackupRestoreStorage() {
+    // Another option is make it a StringEntity type and maintain a String field inside StringEntity
+    @Volatile // backup/restore happens on Binder thread
+    var data: String? = null
+        private set
+
+    fun setData(data: String?) {
+        this.data = data
+        notifyChange(ChangeReason.UPDATE)
+    }
+
+    override val name: String
+        get() = "MyData"
+
+    override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+        listOf(StringEntity("data"))
+
+    private inner class StringEntity(override val key: String) : BackupRestoreEntity {
+        override fun backup(
+            backupContext: BackupContext,
+            outputStream: OutputStream,
+        ) =
+            if (data != null) {
+                outputStream.write(data!!.toByteArray(UTF_8))
+                EntityBackupResult.UPDATE
+            } else {
+                EntityBackupResult.DELETE
+            }
+
+        override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+            data = String(inputStream.readAllBytes(), UTF_8)
+            // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+        }
+    }
+
+    override fun onRestoreFinished() {
+        // TODO: Update state with the restored data. Use this callback instead "restore()" in case
+        //       the restore action involves several entities.
+        // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+    }
+}
+```
+
+In the application class:
+
+```kotlin
+class MyApplication : Application() {
+  override fun onCreate() {
+    super.onCreate();
+    BackupRestoreStorageManager.getInstance(this).add(MyDataStore());
+  }
+}
+```
+
+In the custom `BackupAgentHelper` class:
+
+```kotlin
+class MyBackupAgentHelper : BackupAgentHelper() {
+  override fun onCreate() {
+    BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
+  }
+
+  override fun onRestoreFinished() {
+    BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
+  }
+}
+```
diff --git a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
index 685e259..0ae9c26 100644
--- a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
@@ -19,7 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
-    android:importantForAccessibility="noHideDescendants"
+    android:importantForAccessibility="no"
     android:gravity="center"
     android:orientation="horizontal">
 
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index f4d4dba..815a101 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -33,16 +34,18 @@
 import android.widget.ImageView;
 
 import androidx.annotation.RawRes;
+import androidx.annotation.StringRes;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
 
+import com.android.settingslib.widget.preference.illustration.R;
+
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieDrawable;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
-import com.android.settingslib.widget.preference.illustration.R;
 
 /**
  * IllustrationPreference is a preference that can play lottie format animation
@@ -62,8 +65,8 @@
     private Drawable mImageDrawable;
     private View mMiddleGroundView;
     private OnBindListener mOnBindListener;
-
     private boolean mLottieDynamicColor;
+    private CharSequence mContentDescription;
 
     /**
      * Interface to listen in on when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
@@ -123,7 +126,10 @@
                 (FrameLayout) holder.findViewById(R.id.middleground_layout);
         final LottieAnimationView illustrationView =
                 (LottieAnimationView) holder.findViewById(R.id.lottie_view);
-
+        if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) {
+            illustrationView.setContentDescription(mContentDescription);
+            illustrationView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
         // To solve the problem of non-compliant illustrations, we set the frame height
         // to 300dp and set the length of the short side of the screen to
         // the width of the frame.
@@ -208,6 +214,29 @@
     }
 
     /**
+     * To set content description of the {@link Illustration Preference}. This can use for talkback
+     * environment if developer wants to have a customization content.
+     *
+     * @param contentDescription The CharSequence of the content description.
+     */
+    public void setContentDescription(CharSequence contentDescription) {
+        if (!TextUtils.equals(mContentDescription, contentDescription)) {
+            mContentDescription = contentDescription;
+            notifyChanged();
+        }
+    }
+
+    /**
+     * To set content description of the {@link Illustration Preference}. This can use for talkback
+     * environment if developer wants to have a customization content.
+     *
+     * @param contentDescriptionResId The resource id of the content description.
+     */
+    public void setContentDescription(@StringRes int contentDescriptionResId) {
+        setContentDescription(getContext().getText(contentDescriptionResId));
+    }
+
+    /**
      * Gets the lottie illustration resource id.
      */
     public int getLottieAnimationResId() {
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
index bb518c0..d156f95 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
index bb518c0..d156f95 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
index 10869f2..fe877ee 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index bd27c89..1118efc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -81,11 +81,13 @@
     public static final int BROADCAST_STATE_UNKNOWN = 0;
     public static final int BROADCAST_STATE_ON = 1;
     public static final int BROADCAST_STATE_OFF = 2;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(
             prefix = {"BROADCAST_STATE_"},
             value = {BROADCAST_STATE_UNKNOWN, BROADCAST_STATE_ON, BROADCAST_STATE_OFF})
     public @interface BroadcastState {}
+
     private static final String SETTINGS_PKG = "com.android.settings";
     private static final String TAG = "LocalBluetoothLeBroadcast";
     private static final boolean DEBUG = BluetoothUtils.D;
@@ -1068,7 +1070,7 @@
             return;
         }
         int fallbackActiveGroupId = getFallbackActiveGroupId();
-        if (targetCachedDevice.getGroupId() == fallbackActiveGroupId) {
+        if (getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
             Log.d(
                     TAG,
                     "Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
@@ -1091,6 +1093,23 @@
                 BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
     }
 
+    private int getGroupId(CachedBluetoothDevice cachedDevice) {
+        int groupId = cachedDevice.getGroupId();
+        String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
+        if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+            Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
+            return groupId;
+        }
+        for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+            if (profile instanceof LeAudioProfile) {
+                Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
+                return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
+            }
+        }
+        Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
+        return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+    }
+
     private void notifyBroadcastStateChange(@BroadcastState int state) {
         if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
             Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 0df4615..21cc9a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -161,11 +161,11 @@
 
     override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
         withContext(backgroundCoroutineContext) {
-            if (isMuted) {
-                audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
-            } else {
-                audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
-            }
+            audioManager.adjustStreamVolume(
+                audioStream.value,
+                if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+                0,
+            )
         }
 
     private fun getMinVolume(stream: AudioStream): Int =
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 1586b8f..c9ac97d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -84,10 +84,10 @@
                     (audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION &&
                         audioStreamModel.isMuted)
             ) {
-                return 0
+                return audioStreamModel.minVolume
             }
         } else if (audioStreamModel.isMuted) {
-            return 0
+            return audioStreamModel.minVolume
         }
         return audioStreamModel.volume
     }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 1728a80..9860cd8 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -77,13 +77,13 @@
         `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
         `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
         `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
-            val streamType = it.arguments[1] as Int
-            volumeByStream[it.arguments[0] as Int] = streamType
+            val streamType = it.arguments[0] as Int
+            volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
             triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType)))
         }
         `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
             val streamType = it.arguments[0] as Int
-            isMuteByStream[streamType] = it.arguments[2] == AudioManager.ADJUST_MUTE
+            isMuteByStream[streamType] = it.arguments[1] == AudioManager.ADJUST_MUTE
             triggerEvent(AudioManagerEvent.StreamMuteChanged(AudioStream(streamType)))
         }
         `when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
diff --git a/packages/SettingsProvider/res/values/strings.xml b/packages/SettingsProvider/res/values/strings.xml
index 76bea31..9ca575e 100644
--- a/packages/SettingsProvider/res/values/strings.xml
+++ b/packages/SettingsProvider/res/values/strings.xml
@@ -19,14 +19,4 @@
 <resources>
     <!-- Name of the activity for Settings storage. -->
     <string name="app_label">Settings Storage</string>
-
-    <!-- A notification is shown when the user's softap config has been changed due to underlying
-     hardware restrictions. This is the notifications's title.
-     [CHAR_LIMIT=NONE] -->
-    <string name="wifi_softap_config_change">Hotspot settings have changed</string>
-
-    <!-- A notification is shown when the user's softap config has been changed due to underlying
-         hardware restrictions. This is the notification's summary message.
-         [CHAR_LIMIT=NONE] -->
-    <string name="wifi_softap_config_change_summary">Tap to see details</string>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 7b49608..e5d62f8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -1133,16 +1133,15 @@
             // Depending on device hardware, we may need to notify the user of a setting change
             SoftApConfiguration storedConfig = mWifiManager.getSoftApConfiguration();
 
-            if (isNeedToNotifyUserConfigurationHasChanged(configInCloud, storedConfig)) {
-                Log.d(TAG, "restored ap configuration requires a conversion, notify the user"
+            if (isConfigurationHasChanged(configInCloud, storedConfig)) {
+                Log.d(TAG, "restored ap configuration requires a conversion: "
                         + ", configInCloud is " + configInCloud + " but storedConfig is "
                         + storedConfig);
-                WifiSoftApConfigChangedNotifier.notifyUserOfConfigConversion(this);
             }
         }
     }
 
-    private boolean isNeedToNotifyUserConfigurationHasChanged(SoftApConfiguration configInCloud,
+    private boolean isConfigurationHasChanged(SoftApConfiguration configInCloud,
             SoftApConfiguration storedConfig) {
         // Check if the cloud configuration was modified when restored to the device.
         // All elements of the configuration are compared except:
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
deleted file mode 100644
index dc51c40..0000000
--- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2019 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.providers.settings;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.internal.notification.SystemNotificationChannels;
-
-import java.util.List;
-
-/**
- * Helper class for sending notifications when the user's Soft AP config was changed upon restore.
- */
-public class WifiSoftApConfigChangedNotifier {
-    private WifiSoftApConfigChangedNotifier() {}
-
-    /**
-     * Send a notification informing the user that their' Soft AP Config was changed upon restore.
-     * When the user taps on the notification, they are taken to the Wifi Tethering page in
-     * Settings.
-     */
-    public static void notifyUserOfConfigConversion(Context context) {
-        NotificationManager notificationManager =
-                context.getSystemService(NotificationManager.class);
-
-        // create channel, or update it if it already exists
-        NotificationChannel channel = new NotificationChannel(
-                SystemNotificationChannels.NETWORK_STATUS,
-                context.getString(
-                        com.android.internal.R.string.notification_channel_network_status),
-                NotificationManager.IMPORTANCE_LOW);
-        notificationManager.createNotificationChannel(channel);
-
-        notificationManager.notify(
-                SystemMessageProto.SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED,
-                createConversionNotification(context));
-    }
-
-    private static Notification createConversionNotification(Context context) {
-        Resources resources = context.getResources();
-        CharSequence title = resources.getText(R.string.wifi_softap_config_change);
-        CharSequence contentSummary = resources.getText(R.string.wifi_softap_config_change_summary);
-        int color = resources.getColor(
-                android.R.color.system_notification_accent_color, context.getTheme());
-
-        return new Notification.Builder(context, SystemNotificationChannels.NETWORK_STATUS)
-                .setSmallIcon(R.drawable.ic_wifi_settings)
-                .setPriority(Notification.PRIORITY_HIGH)
-                .setCategory(Notification.CATEGORY_SYSTEM)
-                .setContentTitle(title)
-                .setContentText(contentSummary)
-                .setContentIntent(getPendingActivity(context))
-                .setTicker(title)
-                .setShowWhen(false)
-                .setLocalOnly(true)
-                .setColor(color)
-                .setStyle(new Notification.BigTextStyle()
-                        .setBigContentTitle(title)
-                        .setSummaryText(contentSummary))
-                .setAutoCancel(true)
-                .build();
-    }
-
-    private static PendingIntent getPendingActivity(Context context) {
-        Intent intent = new Intent("com.android.settings.WIFI_TETHER_SETTINGS")
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .setPackage(getSettingsPackageName(context));
-        return PendingIntent.getActivity(context, 0, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-    }
-
-    /**
-     * @return Get settings package name.
-     */
-    private static String getSettingsPackageName(Context context) {
-        if (context == null) return null;
-
-        Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
-        List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentActivitiesAsUser(
-                intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEFAULT_ONLY,
-                UserHandle.of(ActivityManager.getCurrentUser()));
-        if (resolveInfos == null || resolveInfos.isEmpty()) {
-            return "com.android.settings";
-        }
-        return resolveInfos.get(0).activityInfo.packageName;
-    }
-}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eb2d13d..43ea3ec 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -698,6 +698,9 @@
     <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
     <uses-permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" />
 
+    <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+    <uses-permission android:name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
+
     <!-- Permission required for CTS test - CallAudioInterceptionTest -->
     <uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" />
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e62c77dc..3c18f17 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -911,7 +911,7 @@
 
         <activity
             android:name=".volume.panel.ui.activity.VolumePanelActivity"
-            android:label="@string/sound_settings"
+            android:label="@string/accessibility_volume_settings"
             android:excludeFromRecents="true"
             android:exported="false"
             android:launchMode="singleInstance"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 1c98630..e5e3469 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -635,3 +635,11 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "qs_ui_refactor"
+    namespace: "systemui"
+    description: "Enables the new QS UI pipeline that follows recommended architecture and uses"
+      " Compose for the UI."
+    bug: "325099249"
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
index 9a99649..f779cf36 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -62,6 +62,7 @@
 import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
@@ -69,7 +70,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastFirst
 import androidx.compose.ui.util.fastFirstOrNull
-import com.android.compose.theme.LocalAndroidColorScheme
 
 /**
  * Platform slider implementation that displays a slider with an [icon] and a [label] at the start.
@@ -143,12 +143,14 @@
             thumb = { Spacer(Modifier.size(thumbSize)) },
         )
 
-        Spacer(
-            Modifier.padding(8.dp)
-                .size(4.dp)
-                .align(Alignment.CenterEnd)
-                .background(color = colors.indicatorColor, shape = CircleShape)
-        )
+        if (enabled) {
+            Spacer(
+                Modifier.padding(8.dp)
+                    .size(4.dp)
+                    .align(Alignment.CenterEnd)
+                    .background(color = colors.indicatorColor, shape = CircleShape)
+            )
+        }
     }
 }
 
@@ -219,9 +221,9 @@
                     )
                 Box(
                     modifier =
-                        Modifier.layoutId(TrackComponent.Label).offset {
-                            IntOffset(offsetX.toInt(), 0)
-                        },
+                        Modifier.layoutId(TrackComponent.Label)
+                            .offset { IntOffset(offsetX.toInt(), 0) }
+                            .padding(end = 16.dp),
                     contentAlignment = Alignment.CenterStart,
                 ) {
                     CompositionLocalProvider(
@@ -452,11 +454,11 @@
 @Composable
 private fun lightThemePlatformSliderColors() =
     PlatformSliderColors(
-        trackColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
+        trackColor = colorResource(android.R.color.system_accent3_200),
         indicatorColor = MaterialTheme.colorScheme.tertiary,
         iconColor = MaterialTheme.colorScheme.onTertiary,
         labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
-        labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
+        labelColorOnTrack = MaterialTheme.colorScheme.onTertiaryContainer,
         disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -467,11 +469,11 @@
 @Composable
 private fun darkThemePlatformSliderColors() =
     PlatformSliderColors(
-        trackColor = MaterialTheme.colorScheme.tertiary,
+        trackColor = colorResource(android.R.color.system_accent3_600),
         indicatorColor = MaterialTheme.colorScheme.tertiary,
-        iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
+        iconColor = MaterialTheme.colorScheme.onTertiary,
         labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
-        labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
+        labelColorOnTrack = colorResource(android.R.color.system_accent3_900),
         disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
         disabledIconColor = MaterialTheme.colorScheme.outline,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 0d6b710..6a510bd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -85,6 +85,8 @@
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.ColorMatrix
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.pointer.motionEventSpy
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInWindow
@@ -177,33 +179,43 @@
                 }
                 .thenIf(!viewModel.isEditMode) {
                     Modifier.pointerInput(
-                        gridState,
-                        contentOffset,
-                        communalContent,
-                        gridCoordinates
-                    ) {
-                        detectLongPressGesture { offset ->
-                            // Deduct both grid offset relative to its container and content offset.
-                            val adjustedOffset =
-                                gridCoordinates?.let {
-                                    offset - it.positionInWindow() - contentOffset
+                            gridState,
+                            contentOffset,
+                            communalContent,
+                            gridCoordinates
+                        ) {
+                            detectLongPressGesture { offset ->
+                                // Deduct both grid offset relative to its container and content
+                                // offset.
+                                val adjustedOffset =
+                                    gridCoordinates?.let {
+                                        offset - it.positionInWindow() - contentOffset
+                                    }
+                                val index =
+                                    adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+                                // Display the button only when the gesture initiates from widgets,
+                                // the CTA tile, or an empty area on the screen. UMO/smartspace have
+                                // their own long-press handlers. To prevent user confusion, we
+                                // should
+                                // not display this button.
+                                if (
+                                    index == null ||
+                                        communalContent[index].isWidgetContent() ||
+                                        communalContent[index] is
+                                            CommunalContentModel.CtaTileInViewMode
+                                ) {
+                                    isButtonToEditWidgetsShowing = true
                                 }
-                            val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
-                            // Display the button only when the gesture initiates from widgets,
-                            // the CTA tile, or an empty area on the screen. UMO/smartspace have
-                            // their own long-press handlers. To prevent user confusion, we should
-                            // not display this button.
-                            if (
-                                index == null ||
-                                    communalContent[index].isWidgetContent() ||
-                                    communalContent[index] is CommunalContentModel.CtaTileInViewMode
-                            ) {
-                                isButtonToEditWidgetsShowing = true
+                                val key =
+                                    index?.let { keyAtIndexIfEditable(communalContent, index) }
+                                viewModel.setSelectedKey(key)
                             }
-                            val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
-                            viewModel.setSelectedKey(key)
                         }
-                    }
+                        .onPreviewKeyEvent {
+                            onKeyEvent(viewModel)
+                            false
+                        }
+                        .motionEventSpy { onMotionEvent(viewModel) }
                 },
     ) {
         CommunalHubLazyGrid(
@@ -311,6 +323,14 @@
     }
 }
 
+private fun onKeyEvent(viewModel: BaseCommunalViewModel) {
+    viewModel.signalUserInteraction()
+}
+
+private fun onMotionEvent(viewModel: BaseCommunalViewModel) {
+    viewModel.signalUserInteraction()
+}
+
 @Composable
 private fun ScrollOnNewSmartspaceEffect(
     viewModel: BaseCommunalViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 5d9b014..eedff89 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -66,6 +66,7 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.animation.Expandable
+import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.background
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.compose.theme.colorAttr
@@ -77,16 +78,16 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.qs.ui.composable.QuickSettingsTheme
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
 @Composable
-fun FooterActionsWithAnimatedVisibility(
+fun SceneScope.FooterActionsWithAnimatedVisibility(
     viewModel: FooterActionsViewModel,
     isCustomizing: Boolean,
     lifecycleOwner: LifecycleOwner,
-    footerActionsModifier: (Modifier) -> Modifier,
     modifier: Modifier = Modifier,
 ) {
     AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) {
@@ -96,7 +97,7 @@
             FooterActions(
                 viewModel = viewModel,
                 qsVisibilityLifecycleOwner = lifecycleOwner,
-                modifier = footerActionsModifier(Modifier),
+                modifier = Modifier.element(QuickSettings.Elements.FooterActions),
             )
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 6ae1410..5b9213a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -60,7 +60,6 @@
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
 import com.android.systemui.res.R
@@ -249,9 +248,6 @@
                 viewModel = footerActionsViewModel,
                 isCustomizing = isCustomizing,
                 lifecycleOwner = lifecycleOwner,
-                footerActionsModifier = { modifier ->
-                    modifier.element(QuickSettings.Elements.FooterActions)
-                },
                 modifier = Modifier.align(Alignment.CenterHorizontally),
             )
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 31201c2f..15e7b51 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -347,9 +347,6 @@
                         viewModel = footerActionsViewModel,
                         isCustomizing = isCustomizing,
                         lifecycleOwner = lifecycleOwner,
-                        footerActionsModifier = { modifier ->
-                            modifier.element(QuickSettings.Elements.FooterActions)
-                        },
                         modifier = Modifier.align(Alignment.CenterHorizontally),
                     )
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 5f7bd47..b721e41 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -32,6 +32,11 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Expandable
@@ -52,6 +57,7 @@
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
         val viewModelByState by viewModelFlow.collectAsState()
         val viewModel = viewModelByState ?: return
+        val label = viewModel.label.toString()
 
         Column(
             modifier = modifier,
@@ -59,7 +65,11 @@
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
             Expandable(
-                modifier = Modifier.height(64.dp).fillMaxWidth(),
+                modifier =
+                    Modifier.height(64.dp).fillMaxWidth().semantics {
+                        role = Role.Button
+                        contentDescription = label
+                    },
                 color = MaterialTheme.colorScheme.primaryContainer,
                 shape = RoundedCornerShape(28.dp),
                 contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
@@ -71,7 +81,8 @@
                 }
             }
             Text(
-                text = viewModel.label.toString(),
+                modifier = Modifier.clearAndSetSemantics {},
+                text = label,
                 style = MaterialTheme.typography.labelMedium,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index dfee684..28fd785 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -32,6 +32,9 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import com.android.systemui.common.ui.compose.Icon
@@ -50,13 +53,16 @@
     override fun VolumePanelComposeScope.Content(modifier: Modifier) {
         val viewModelByState by viewModelFlow.collectAsState()
         val viewModel = viewModelByState ?: return
+        val label = viewModel.label.toString()
+
         Column(
             modifier = modifier,
             verticalArrangement = Arrangement.spacedBy(12.dp),
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
             OutlinedIconToggleButton(
-                modifier = Modifier.height(64.dp).fillMaxWidth(),
+                modifier =
+                    Modifier.height(64.dp).fillMaxWidth().semantics { contentDescription = label },
                 checked = viewModel.isChecked,
                 onCheckedChange = onCheckedChange,
                 shape = RoundedCornerShape(28.dp),
@@ -72,7 +78,8 @@
                 Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
             }
             Text(
-                text = viewModel.label.toString(),
+                modifier = Modifier.clearAndSetSemantics {},
+                text = label,
                 style = MaterialTheme.typography.labelMedium,
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index 53de5bc..6f2ed81 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -49,10 +49,16 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.LiveRegionMode
+import androidx.compose.ui.semantics.liveRegion
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.common.ui.compose.toColor
+import com.android.systemui.res.R
 import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.ConnectedDeviceViewModel
 import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.DeviceIconViewModel
 import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.MediaOutputViewModel
@@ -74,14 +80,19 @@
             viewModel.connectedDeviceViewModel.collectAsState()
         val deviceIconViewModel: DeviceIconViewModel? by
             viewModel.deviceIconViewModel.collectAsState()
+        val clickLabel = stringResource(R.string.volume_panel_enter_media_output_settings)
 
         Expandable(
-            modifier = Modifier.fillMaxWidth().height(80.dp),
+            modifier =
+                Modifier.fillMaxWidth().height(80.dp).semantics {
+                    liveRegion = LiveRegionMode.Polite
+                    this.onClick(label = clickLabel) { false }
+                },
             color = MaterialTheme.colorScheme.surface,
             shape = RoundedCornerShape(28.dp),
             onClick = { viewModel.onBarClick(it) },
         ) { _ ->
-            Row(verticalAlignment = Alignment.CenterVertically) {
+            Row(modifier = Modifier, verticalAlignment = Alignment.CenterVertically) {
                 connectedDeviceViewModel?.let { ConnectedDeviceText(it) }
 
                 deviceIconViewModel?.let { ConnectedDeviceIcon(it) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index 89251939..26086d1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -24,13 +24,16 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
 import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import com.android.compose.PlatformIconButton
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.res.R
@@ -101,16 +104,19 @@
                 }
             }
 
-            PlatformIconButton(
+            IconButton(
                 modifier = Modifier.align(Alignment.TopEnd).size(64.dp).padding(20.dp),
-                iconResource = R.drawable.ic_close,
-                contentDescription = null,
                 onClick = { dialog.dismiss() },
                 colors =
                     IconButtonDefaults.iconButtonColors(
                         contentColor = MaterialTheme.colorScheme.outline
                     )
-            )
+            ) {
+                Icon(
+                    painterResource(R.drawable.ic_close),
+                    contentDescription = stringResource(R.string.accessibility_desc_close),
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 81d2da0..e1cf3a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -45,6 +45,11 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformSliderColors
 import com.android.systemui.res.R
@@ -76,16 +81,26 @@
             VolumeSlider(
                 modifier = Modifier.weight(1f),
                 state = sliderState,
-                onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+                onValueChange = { newValue: Float ->
+                    sliderViewModel.onValueChanged(sliderState, newValue)
+                },
+                onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
             )
 
+            val expandButtonStateDescription =
+                if (isExpanded) stringResource(R.string.volume_panel_expanded_sliders)
+                else stringResource(R.string.volume_panel_collapsed_sliders)
             if (isExpandable) {
                 ExpandButton(
+                    modifier =
+                        Modifier.semantics {
+                            role = Role.Switch
+                            stateDescription = expandButtonStateDescription
+                        },
                     isExpanded = isExpanded,
                     onExpandedChanged = onExpandedChanged,
-                    sliderColors,
-                    Modifier,
+                    sliderColors = sliderColors,
                 )
             }
         }
@@ -114,9 +129,10 @@
                         VolumeSlider(
                             modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
                             state = sliderState,
-                            onValueChangeFinished = {
-                                sliderViewModel.onValueChangeFinished(sliderState, it)
+                            onValueChange = { newValue: Float ->
+                                sliderViewModel.onValueChanged(sliderState, newValue)
                             },
+                            onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                             sliderColors = sliderColors,
                         )
                     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index 910ee72..b284c69 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -43,7 +43,10 @@
             VolumeSlider(
                 modifier = Modifier.fillMaxWidth(),
                 state = sliderState,
-                onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+                onValueChange = { newValue: Float ->
+                    sliderViewModel.onValueChanged(sliderState, newValue)
+                },
+                onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
             )
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 3e0aee5..fa94ea0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -18,19 +18,27 @@
 
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.expandVertically
-import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.size
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonColors
+import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.ProgressBarRangeInfo
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.disabled
+import androidx.compose.ui.semantics.progressBarRangeInfo
+import androidx.compose.ui.semantics.setProgress
 import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformSlider
 import com.android.compose.PlatformSliderColors
@@ -40,23 +48,61 @@
 @Composable
 fun VolumeSlider(
     state: SliderState,
-    onValueChangeFinished: (Float) -> Unit,
+    onValueChange: (newValue: Float) -> Unit,
+    onIconTapped: () -> Unit,
     modifier: Modifier = Modifier,
     sliderColors: PlatformSliderColors,
 ) {
-    var value by remember(state.value) { mutableFloatStateOf(state.value) }
+    val value by
+        animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
     PlatformSlider(
-        modifier = modifier,
+        modifier =
+            modifier.clearAndSetSemantics {
+                if (!state.isEnabled) disabled()
+                contentDescription = state.label
+
+                // provide a not animated value to the a11y because it fails to announce the settled
+                // value when it changes rapidly.
+                progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+                setProgress { targetValue ->
+                    val targetDirection =
+                        when {
+                            targetValue > value -> 1
+                            targetValue < value -> -1
+                            else -> 0
+                        }
+
+                    val newValue =
+                        (value + targetDirection * state.a11yStep).coerceIn(
+                            state.valueRange.start,
+                            state.valueRange.endInclusive
+                        )
+                    onValueChange(newValue)
+                    true
+                }
+            },
         value = value,
         valueRange = state.valueRange,
-        onValueChange = { value = it },
-        onValueChangeFinished = { onValueChangeFinished(value) },
+        onValueChange = onValueChange,
         enabled = state.isEnabled,
         icon = { isDragging ->
             if (isDragging) {
-                Text(text = value.toInt().toString())
+                Text(text = value.toInt().toString(), color = LocalContentColor.current)
             } else {
-                state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) }
+                state.icon?.let {
+                    IconButton(
+                        onClick = onIconTapped,
+                        colors =
+                            IconButtonColors(
+                                contentColor = LocalContentColor.current,
+                                containerColor = Color.Transparent,
+                                disabledContentColor = LocalContentColor.current,
+                                disabledContainerColor = Color.Transparent,
+                            )
+                    ) {
+                        Icon(modifier = Modifier.size(24.dp), icon = it)
+                    }
+                }
             }
         },
         colors = sliderColors,
@@ -66,19 +112,21 @@
                     modifier = Modifier.basicMarquee(),
                     text = state.label,
                     style = MaterialTheme.typography.titleMedium,
+                    color = LocalContentColor.current,
                     maxLines = 1,
                 )
 
                 state.disabledMessage?.let { message ->
                     AnimatedVisibility(
                         !state.isEnabled,
-                        enter = expandVertically { it },
-                        exit = shrinkVertically { it },
+                        enter = slideInVertically { it },
+                        exit = slideOutVertically { it },
                     ) {
                         Text(
                             modifier = Modifier.basicMarquee(),
                             text = message,
                             style = MaterialTheme.typography.bodySmall,
+                            color = LocalContentColor.current,
                             maxLines = 1,
                         )
                     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index adf4fc6..b253309 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -140,6 +140,14 @@
     }
 
     @Test
+    fun canShowAlternateBouncerForFingerprint_primaryBouncerShowing() {
+        givenCanShowAlternateBouncer()
+        bouncerRepository.setPrimaryShow(true)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
     fun show_whenCannotShow() {
         givenCannotShowAlternateBouncer()
 
@@ -202,7 +210,7 @@
         } else {
             bouncerRepository.setAlternateBouncerUIAvailable(true)
         }
-
+        bouncerRepository.setPrimaryShow(false)
         biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
         biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
         whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index ce6445b..43266bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal
 
+import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -25,13 +26,17 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dock.dockManager
 import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
@@ -54,11 +59,15 @@
     @Before
     fun setUp() {
         with(kosmos) {
+            fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT)
+
             underTest =
                 CommunalSceneStartable(
                         dockManager = dockManager,
                         communalInteractor = communalInteractor,
                         keyguardTransitionInteractor = keyguardTransitionInteractor,
+                        keyguardInteractor = keyguardInteractor,
+                        systemSettings = fakeSettings,
                         applicationScope = applicationCoroutineScope,
                         bgScope = applicationCoroutineScope,
                     )
@@ -246,6 +255,132 @@
             }
         }
 
+    @Test
+    fun hubTimeout_whenDreaming_goesToBlank() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is dreaming and on communal.
+                fakeKeyguardRepository.setDreaming(true)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Scene times out back to blank after the screen timeout.
+                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+            }
+        }
+
+    @Test
+    fun hubTimeout_notDreaming_staysOnCommunal() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is not dreaming and on communal.
+                fakeKeyguardRepository.setDreaming(false)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                // Scene stays as Communal
+                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            }
+        }
+
+    @Test
+    fun hubTimeout_dreamStopped_staysOnCommunal() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is dreaming and on communal.
+                fakeKeyguardRepository.setDreaming(true)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Wait a bit, but not long enough to timeout.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Dream stops, timeout is cancelled and device stays on hub, because the regular
+                // screen timeout will take effect at this point.
+                fakeKeyguardRepository.setDreaming(false)
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            }
+        }
+
+    @Test
+    fun hubTimeout_dreamStartedHalfway_goesToCommunal() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is on communal, but not dreaming.
+                fakeKeyguardRepository.setDreaming(false)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Wait a bit, but not long enough to timeout, then start dreaming.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                fakeKeyguardRepository.setDreaming(true)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Device times out after one screen timeout interval, dream doesn't reset timeout.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+            }
+        }
+
+    @Test
+    fun hubTimeout_userActivityTriggered_resetsTimeout() =
+        with(kosmos) {
+            testScope.runTest {
+                // Device is dreaming and on communal.
+                fakeKeyguardRepository.setDreaming(true)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Wait a bit, but not long enough to timeout.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+
+                // Send user interaction to reset timeout.
+                communalInteractor.signalUserInteraction()
+
+                // If user activity didn't reset timeout, we would have gone back to Blank by now.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Timeout happens one interval after the user interaction.
+                advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+            }
+        }
+
+    @Test
+    fun hubTimeout_screenTimeoutChanged() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
+
+                // Device is dreaming and on communal.
+                fakeKeyguardRepository.setDreaming(true)
+                communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                // Scene times out back to blank after the screen timeout.
+                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+                advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+            }
+        }
+
     private fun TestScope.updateDocked(docked: Boolean) =
         with(kosmos) {
             runCurrent()
@@ -260,4 +395,8 @@
             setCommunalAvailable(true)
             runCurrent()
         }
+
+    companion object {
+        private const val SCREEN_TIMEOUT = 1000
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index cd4db2f..769caaa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -47,9 +47,7 @@
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 @android.platform.test.annotations.EnabledOnRavenwood
 class KeyguardTransitionInteractorTest : SysuiTestCase() {
-
     val kosmos = testKosmos()
-
     val underTest = kosmos.keyguardTransitionInteractor
     val repository = kosmos.fakeKeyguardTransitionRepository
     val testScope = kosmos.testScope
@@ -242,34 +240,35 @@
     }
 
     @Test
-    fun transitionValue() = runTest {
-        val startedSteps by collectValues(underTest.transitionValue(state = DOZING))
+    fun transitionValue() =
+        testScope.runTest {
+            val startedSteps by collectValues(underTest.transitionValue(state = DOZING))
 
-        val toSteps =
-            listOf(
-                TransitionStep(AOD, DOZING, 0f, STARTED),
-                TransitionStep(AOD, DOZING, 0.5f, RUNNING),
-                TransitionStep(AOD, DOZING, 1f, FINISHED),
-            )
-        toSteps.forEach {
-            repository.sendTransitionStep(it)
-            runCurrent()
+            val toSteps =
+                listOf(
+                    TransitionStep(AOD, DOZING, 0f, STARTED),
+                    TransitionStep(AOD, DOZING, 0.5f, RUNNING),
+                    TransitionStep(AOD, DOZING, 1f, FINISHED),
+                )
+            toSteps.forEach {
+                repository.sendTransitionStep(it)
+                runCurrent()
+            }
+
+            val fromSteps =
+                listOf(
+                    TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+                    TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+                    TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED),
+                )
+            fromSteps.forEach {
+                repository.sendTransitionStep(it)
+                runCurrent()
+            }
+
+            assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
         }
 
-        val fromSteps =
-            listOf(
-                TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
-                TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
-                TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED),
-            )
-        fromSteps.forEach {
-            repository.sendTransitionStep(it)
-            runCurrent()
-        }
-
-        assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
-    }
-
     @Test
     fun isInTransitionToAnyState() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index de659cf..f517cec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -81,12 +81,12 @@
     }
 
     @Test
-    fun movement_initializedToZero() =
+    fun movement_initializedToDefaultValues() =
         testScope.runTest {
             val movement by collectLastValue(underTest.movement(burnInParameters))
             assertThat(movement?.translationY).isEqualTo(0)
             assertThat(movement?.translationX).isEqualTo(0)
-            assertThat(movement?.scale).isEqualTo(0f)
+            assertThat(movement?.scale).isEqualTo(1f)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
index 64125f1..af96780 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
@@ -99,6 +99,31 @@
             values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
         }
 
+    @Test
+    fun lockscreenTranslationX_resetsAfterCancellation() =
+        testScope.runTest {
+            configurationRepository.setDimensionPixelSize(
+                R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
+                100
+            )
+            val values by collectValues(underTest.keyguardTranslationX)
+            assertThat(values).isEmpty()
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    step(0f, TransitionState.STARTED),
+                    step(0.3f),
+                    step(0.5f),
+                    step(0.9f, TransitionState.CANCELED)
+                ),
+                testScope,
+            )
+
+            assertThat(values).hasSize(4)
+            values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
+            assertThat(values.last().value).isEqualTo(0f)
+        }
+
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 979d504..6e15c51 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -86,6 +86,13 @@
     }
 
     @Test
+    fun defaultBurnInScaleEqualsOne() =
+        testScope.runTest {
+            val burnInScale by collectLastValue(underTest.scale)
+            assertThat(burnInScale!!.scale).isEqualTo(1f)
+        }
+
+    @Test
     fun burnInLayerVisibility() =
         testScope.runTest {
             val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
@@ -250,6 +257,17 @@
 
             keyguardRepository.topClippingBounds.value = 1000
             assertThat(topClippingBounds).isEqualTo(1000)
+
+            // Run at least 1 transition to make sure value remains at 0
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+
+            // Make sure the value hasn't changed since we're GONE
+            keyguardRepository.topClippingBounds.value = 5
+            assertThat(topClippingBounds).isEqualTo(1000)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 278bf9b..53a8e5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -721,39 +722,36 @@
     @Test
     fun shadeCollapseFadeIn() =
         testScope.runTest {
-            val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+            val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
 
             // Start on lockscreen without the shade
-            underTest.setShadeCollapseFadeInComplete(false)
             showLockscreen()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... then the shade expands
             showLockscreenWithShadeExpanded()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... it collapses
             showLockscreen()
-            assertThat(fadeIn).isEqualTo(true)
+            assertThat(fadeIn[1]).isEqualTo(true)
 
-            // ... now send animation complete signal
-            underTest.setShadeCollapseFadeInComplete(true)
-            assertThat(fadeIn).isEqualTo(false)
+            // ... and ensure the value goes back to false
+            assertThat(fadeIn[2]).isEqualTo(false)
         }
 
     @Test
     fun shadeCollapseFadeIn_doesNotRunIfTransitioningToAod() =
         testScope.runTest {
-            val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+            val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
 
             // Start on lockscreen without the shade
-            underTest.setShadeCollapseFadeInComplete(false)
             showLockscreen()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... then the shade expands
             showLockscreenWithShadeExpanded()
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
 
             // ... then user hits power to go to AOD
             keyguardTransitionRepository.sendTransitionSteps(
@@ -764,7 +762,7 @@
             // ... followed by a shade collapse
             showLockscreen()
             // ... does not trigger a fade in
-            assertThat(fadeIn).isEqualTo(false)
+            assertThat(fadeIn[0]).isEqualTo(false)
         }
 
     private suspend fun TestScope.showLockscreen() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
new file mode 100644
index 0000000..183a58a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.policy
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationThrottleHun.FLAG_NAME)
+class AvalancheControllerTest : SysuiTestCase() {
+
+    private val mAvalancheController = AvalancheController()
+
+    // For creating mocks
+    @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
+    @Mock private val runnableMock: Runnable? = null
+
+    // For creating TestableHeadsUpManager
+    @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
+    private val mUiEventLoggerFake = UiEventLoggerFake()
+    private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+    private val mGlobalSettings = FakeGlobalSettings()
+    private val mSystemClock = FakeSystemClock()
+    private val mExecutor = FakeExecutor(mSystemClock)
+    private var testableHeadsUpManager: BaseHeadsUpManager? = null
+
+    @Before
+    fun setUp() {
+        // Use default non-a11y timeout
+        Mockito.`when`(
+                mAccessibilityMgr!!.getRecommendedTimeoutMillis(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.anyInt()
+                )
+            )
+            .then { i: InvocationOnMock -> i.getArgument(0) }
+
+        // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null
+        testableHeadsUpManager =
+            TestableHeadsUpManager(
+                mContext,
+                mLogger,
+                mExecutor,
+                mGlobalSettings,
+                mSystemClock,
+                mAccessibilityMgr,
+                mUiEventLoggerFake,
+                mAvalancheController
+            )
+    }
+
+    private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+        val entry = testableHeadsUpManager!!.createHeadsUpEntry()
+
+        entry.setEntry(
+            NotificationEntryBuilder()
+                .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
+                .build()
+        )
+        return entry
+    }
+
+    @Test
+    fun testUpdate_isShowing_runsRunnable() {
+        // Entry is showing
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was run
+        Mockito.verify(runnableMock, Mockito.times(1)).run()
+    }
+
+    @Test
+    fun testUpdate_noneShowingAndNotNext_showNow() {
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+
+        // None showing
+        mAvalancheController.headsUpEntryShowing = null
+
+        // Entry is NOT next
+        mAvalancheController.clearNext()
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Entry is showing now
+        Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(headsUpEntry)
+    }
+
+    @Test
+    fun testUpdate_isNext_addsRunnable() {
+        // Another entry is already showing
+        val otherShowingEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+        // Entry is next
+        val headsUpEntry = createHeadsUpEntry(id = 1)
+        mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+        // Entry has one Runnable
+        val runnableList: List<Runnable?>? = mAvalancheController.nextMap[headsUpEntry]
+        Truth.assertThat(runnableList).isNotNull()
+        Truth.assertThat(runnableList!!.size).isEqualTo(1)
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock, "testLabel")
+
+        // Entry has two Runnables
+        Truth.assertThat(runnableList.size).isEqualTo(2)
+    }
+
+    @Test
+    fun testUpdate_isNotNextWithOtherHunShowing_isNext() {
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+
+        // Another entry is already showing
+        val otherShowingEntry = createHeadsUpEntry(id = 1)
+        mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+        // Entry is NOT next
+        mAvalancheController.clearNext()
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Entry is next
+        Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isTrue()
+    }
+
+    @Test
+    fun testDelete_isNext_removedFromNext_runnableNotRun() {
+        // Entry is next
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock, "testLabel")
+
+        // Entry was removed from next
+        Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isFalse()
+
+        // Runnable was not run
+        Mockito.verify(runnableMock, Mockito.times(0)).run()
+    }
+
+    @Test
+    fun testDelete_wasDropped_removedFromDropSet() {
+        // Entry was dropped
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.debugDropSet.add(headsUpEntry)
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Entry was removed from dropSet
+        Truth.assertThat(mAvalancheController.debugDropSet.contains(headsUpEntry)).isFalse()
+    }
+
+    @Test
+    fun testDelete_wasDropped_runnableNotRun() {
+        // Entry was dropped
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.debugDropSet.add(headsUpEntry)
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was not run
+        Mockito.verify(runnableMock, Mockito.times(0)).run()
+    }
+
+    @Test
+    fun testDelete_isShowing_runnableRun() {
+        // Entry is showing
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was run
+        Mockito.verify(runnableMock, Mockito.times(1)).run()
+    }
+
+    @Test
+    fun testDelete_isShowing_showNext() {
+        // Entry is showing
+        val showingEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = showingEntry
+
+        // There's another entry waiting to show next
+        val nextEntry = createHeadsUpEntry(id = 1)
+        mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+        // Delete
+        mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+
+        // Next entry is shown
+        Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index db4d42f..830bcef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -35,13 +35,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Intent;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 import android.testing.TestableLooper;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -77,6 +74,8 @@
 
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
+    private AvalancheController mAvalancheController = new AvalancheController();
+
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
     protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
@@ -99,7 +98,7 @@
 
     private BaseHeadsUpManager createHeadsUpManager() {
         return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
-                mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
+                mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController);
     }
 
     private NotificationEntry createStickyEntry(int id) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index c032d7c..61a79d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -28,8 +28,8 @@
 import android.content.Context;
 import android.testing.TestableLooper;
 
-import androidx.test.filters.SmallTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -76,6 +76,7 @@
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private JavaAdapter mJavaAdapter;
     @Mock private ShadeInteractor mShadeInteractor;
+    private AvalancheController mAvalancheController = new AvalancheController();
 
     private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
         TestableHeadsUpManagerPhone(
@@ -92,7 +93,8 @@
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger,
                 JavaAdapter javaAdapter,
-                ShadeInteractor shadeInteractor
+                ShadeInteractor shadeInteractor,
+                AvalancheController avalancheController
         ) {
             super(
                     context,
@@ -109,7 +111,8 @@
                     accessibilityManagerWrapper,
                     uiEventLogger,
                     javaAdapter,
-                    shadeInteractor
+                    shadeInteractor,
+                    avalancheController
             );
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
@@ -131,12 +134,15 @@
                 mAccessibilityManagerWrapper,
                 mUiEventLogger,
                 mJavaAdapter,
-                mShadeInteractor
+                mShadeInteractor,
+                mAvalancheController
         );
     }
 
     @Before
     public void setUp() {
+        // TODO(b/315362456) create separate test with the flag disabled
+        //  then modify this file to test with the flag enabled
         mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
 
         when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 2747629..d8f77f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -43,9 +43,10 @@
             GlobalSettings globalSettings,
             SystemClock systemClock,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            AvalancheController avalancheController) {
         super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
-                executor, accessibilityManagerWrapper, uiEventLogger);
+                executor, accessibilityManagerWrapper, uiEventLogger, avalancheController);
 
         mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
         mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index e06efe8..3d93654 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -135,7 +135,7 @@
     }
 
     @Test
-    fun streamIsMuted_getStream_volumeZero() {
+    fun streamIsMuted_getStream_volumeMin() {
         with(kosmos) {
             testScope.runTest {
                 val model by collectLastValue(underTest.getAudioStream(audioStream))
@@ -166,7 +166,7 @@
     }
 
     @Test
-    fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsZero() {
+    fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsMin() {
         with(kosmos) {
             testScope.runTest {
                 audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
@@ -184,7 +184,7 @@
     }
 
     @Test
-    fun ringerModeVibrate_getRingerStream_volumeIsZero() {
+    fun ringerModeVibrate_getRingerStream_volumeIsMin() {
         with(kosmos) {
             testScope.runTest {
                 audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
index 449e8bf..1ed7f5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.volume.panel.component.spatial.domain
 
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
 import android.media.session.MediaSession
 import android.media.session.PlaybackState
 import android.testing.TestableLooper.RunWithLooper
@@ -49,26 +51,27 @@
 class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
-    private val cachedBluetoothDevice: CachedBluetoothDevice = mock {
-        whenever(address).thenReturn("test_address")
-    }
-    private val bluetoothMediaDevice: BluetoothMediaDevice = mock {
-        whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
-    }
-
     private lateinit var underTest: SpatialAudioAvailabilityCriteria
 
     @Before
     fun setup() {
         with(kosmos) {
-            mediaControllerRepository.setActiveLocalMediaController(
-                mediaController.apply {
-                    whenever(packageName).thenReturn("test.pkg")
-                    whenever(sessionToken).thenReturn(MediaSession.Token(0, mock {}))
-                    whenever(playbackState).thenReturn(PlaybackState.Builder().build())
+            val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+                whenever(address).thenReturn("test_address")
+            }
+            localMediaRepository.updateCurrentConnectedDevice(
+                mock<BluetoothMediaDevice> {
+                    whenever(name).thenReturn("test_device")
+                    whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
                 }
             )
 
+            whenever(mediaController.packageName).thenReturn("test.pkg")
+            whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+            whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
+
+            mediaControllerRepository.setActiveLocalMediaController(mediaController)
+
             underTest = SpatialAudioAvailabilityCriteria(spatialAudioComponentInteractor)
         }
     }
@@ -77,9 +80,8 @@
     fun noSpatialAudio_noHeadTracking_unavailable() {
         with(kosmos) {
             testScope.runTest {
-                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
-                spatializerRepository.defaultHeadTrackingAvailable = false
-                spatializerRepository.defaultSpatialAudioAvailable = false
+                spatializerRepository.setIsSpatialAudioAvailable(headset, false)
+                spatializerRepository.setIsHeadTrackingAvailable(headset, false)
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
                 runCurrent()
@@ -93,9 +95,8 @@
     fun spatialAudio_noHeadTracking_available() {
         with(kosmos) {
             testScope.runTest {
-                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
-                spatializerRepository.defaultHeadTrackingAvailable = false
-                spatializerRepository.defaultSpatialAudioAvailable = true
+                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+                spatializerRepository.setIsHeadTrackingAvailable(headset, false)
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
                 runCurrent()
@@ -109,9 +110,8 @@
     fun spatialAudio_headTracking_available() {
         with(kosmos) {
             testScope.runTest {
-                localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
-                spatializerRepository.defaultHeadTrackingAvailable = true
-                spatializerRepository.defaultSpatialAudioAvailable = true
+                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+                spatializerRepository.setIsHeadTrackingAvailable(headset, true)
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
                 runCurrent()
@@ -125,8 +125,8 @@
     fun spatialAudio_headTracking_noDevice_unavailable() {
         with(kosmos) {
             testScope.runTest {
-                spatializerRepository.defaultHeadTrackingAvailable = true
-                spatializerRepository.defaultSpatialAudioAvailable = true
+                localMediaRepository.updateCurrentConnectedDevice(null)
+                spatializerRepository.setIsSpatialAudioAvailable(headset, false)
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
                 runCurrent()
@@ -135,4 +135,13 @@
             }
         }
     }
+
+    private companion object {
+        val headset =
+            AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                "test_address"
+            )
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index 06ae220..281b03d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -26,6 +26,7 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.media.BluetoothMediaDevice
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.spatializerInteractor
@@ -37,6 +38,7 @@
 import com.android.systemui.volume.mediaController
 import com.android.systemui.volume.mediaControllerRepository
 import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -74,16 +76,6 @@
 
             mediaControllerRepository.setActiveLocalMediaController(mediaController)
 
-            spatializerRepository.setIsSpatialAudioAvailable(
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_BLE_HEADSET,
-                    "test_address"
-                ),
-                true
-            )
-            spatializerRepository.defaultHeadTrackingAvailable = true
-
             underTest =
                 SpatialAudioComponentInteractor(
                     mediaOutputInteractor,
@@ -97,6 +89,7 @@
     fun setEnabled_changesIsEnabled() {
         with(kosmos) {
             testScope.runTest {
+                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
                 val values by collectValues(underTest.isEnabled)
 
                 underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
@@ -108,6 +101,7 @@
 
                 assertThat(values)
                     .containsExactly(
+                        SpatialAudioEnabledModel.Unknown,
                         SpatialAudioEnabledModel.Disabled,
                         SpatialAudioEnabledModel.HeadTrackingEnabled,
                         SpatialAudioEnabledModel.SpatialAudioEnabled,
@@ -116,4 +110,92 @@
             }
         }
     }
+
+    @Test
+    fun connectedDeviceSupports_isAvailable_SpatialAudio() {
+        with(kosmos) {
+            testScope.runTest {
+                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+
+                val isAvailable by collectLastValue(underTest.isAvailable)
+
+                assertThat(isAvailable)
+                    .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java)
+            }
+        }
+    }
+
+    @Test
+    fun connectedDeviceSupportsHeadTracking_isAvailable_HeadTracking() {
+        with(kosmos) {
+            testScope.runTest {
+                spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+                spatializerRepository.setIsHeadTrackingAvailable(headset, true)
+
+                val isAvailable by collectLastValue(underTest.isAvailable)
+
+                assertThat(isAvailable)
+                    .isInstanceOf(SpatialAudioAvailabilityModel.HeadTracking::class.java)
+            }
+        }
+    }
+
+    @Test
+    fun connectedDeviceDoesntSupport_isAvailable_Unavailable() {
+        with(kosmos) {
+            testScope.runTest {
+                spatializerRepository.setIsSpatialAudioAvailable(headset, false)
+
+                val isAvailable by collectLastValue(underTest.isAvailable)
+
+                assertThat(isAvailable)
+                    .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java)
+            }
+        }
+    }
+
+    @Test
+    fun noConnectedDeviceBuiltinSupports_isAvailable_SpatialAudio() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(null)
+                spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, true)
+
+                val isAvailable by collectLastValue(underTest.isAvailable)
+
+                assertThat(isAvailable)
+                    .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java)
+            }
+        }
+    }
+
+    @Test
+    fun noConnectedDeviceBuiltinDoesntSupport_isAvailable_Unavailable() {
+        with(kosmos) {
+            testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(null)
+                spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, false)
+
+                val isAvailable by collectLastValue(underTest.isAvailable)
+
+                assertThat(isAvailable)
+                    .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java)
+            }
+        }
+    }
+
+    private companion object {
+        val headset =
+            AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                "test_address"
+            )
+        val builtinSpeaker =
+            AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                ""
+            )
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
index a1e4fca..79d3fe9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
@@ -30,28 +30,8 @@
     private val underTest = VolumeSliderInteractor()
 
     @Test
-    fun translateValueToVolume() {
-        assertThat(underTest.translateValueToVolume(30f, volumeRange)).isEqualTo(3)
-    }
-
-    @Test
-    fun processVolumeToValue_muted_zero() {
-        assertThat(underTest.processVolumeToValue(3, volumeRange, null, true)).isEqualTo(0)
-    }
-
-    @Test
-    fun processVolumeToValue_currentValue_currentValue() {
-        assertThat(underTest.processVolumeToValue(3, volumeRange, 30f, false)).isEqualTo(30f)
-    }
-
-    @Test
-    fun processVolumeToValue_currentValueDiffersVolume_returnsTranslatedVolume() {
-        assertThat(underTest.processVolumeToValue(1, volumeRange, 60f, false)).isEqualTo(10f)
-    }
-
-    @Test
-    fun processVolumeToValue_currentValueDiffersNotEnoughVolume_returnsTranslatedVolume() {
-        assertThat(underTest.processVolumeToValue(1, volumeRange, 12f, false)).isEqualTo(12f)
+    fun processVolumeToValue_returnsTranslatedVolume() {
+        assertThat(underTest.processVolumeToValue(2, volumeRange)).isEqualTo(20f)
     }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index 71866b3..82ce6d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -37,7 +37,7 @@
         DefaultComponentsLayoutManager(
             BOTTOM_BAR,
             headerComponents = listOf(COMPONENT_1),
-            footerComponents = listOf(COMPONENT_2),
+            footerComponents = listOf(COMPONENT_5, COMPONENT_2),
         )
 
     @Test
@@ -48,10 +48,18 @@
         val component2 = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
         val component3 = ComponentState(COMPONENT_3, kosmos.mockVolumePanelUiComponent, false)
         val component4 = ComponentState(COMPONENT_4, kosmos.mockVolumePanelUiComponent, false)
+        val component5 = ComponentState(COMPONENT_5, kosmos.mockVolumePanelUiComponent, false)
         val layout =
             underTest.layout(
                 VolumePanelState(0, false, false),
-                setOf(bottomBarComponentState, component1, component2, component3, component4)
+                setOf(
+                    bottomBarComponentState,
+                    component1,
+                    component2,
+                    component3,
+                    component4,
+                    component5,
+                )
             )
 
         Truth.assertThat(layout.bottomBarComponent).isEqualTo(bottomBarComponentState)
@@ -59,7 +67,7 @@
             .containsExactlyElementsIn(listOf(component1))
             .inOrder()
         Truth.assertThat(layout.footerComponents)
-            .containsExactlyElementsIn(listOf(component2))
+            .containsExactlyElementsIn(listOf(component5, component2))
             .inOrder()
         Truth.assertThat(layout.contentComponents)
             .containsExactlyElementsIn(listOf(component3, component4))
@@ -85,5 +93,6 @@
         const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
         const val COMPONENT_3: VolumePanelComponentKey = "test_component:3"
         const val COMPONENT_4: VolumePanelComponentKey = "test_component:4"
+        const val COMPONENT_5: VolumePanelComponentKey = "test_component:5"
     }
 }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b8f20f6..e3a5e15 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1556,11 +1556,11 @@
     <string name="volume_panel_noise_control_title">Noise Control</string>
     <!-- Label for button to enabled/disable spatial audio [CHAR_LIMIT=30] -->
     <string name="volume_panel_spatial_audio_title">Spatial Audio</string>
-    <!-- Label for button to disable spatial audio [CHAR_LIMIT=20] -->
+    <!-- Label for a spatial audio button for the case when it is disabled [CHAR_LIMIT=20] -->
     <string name="volume_panel_spatial_audio_off">Off</string>
-    <!-- Label for button to enabled spatial audio [CHAR_LIMIT=20] -->
+    <!-- Label for a spatial audio button for the case when it is enabled without head tracking [CHAR_LIMIT=20] -->
     <string name="volume_panel_spatial_audio_fixed">Fixed</string>
-    <!-- Label for button to enabled head tracking [CHAR_LIMIT=20] -->
+    <!-- Label for a spatial audio button for the case when it is enabled with head tracking [CHAR_LIMIT=20] -->
     <string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
 
     <string name="volume_ringer_change">Tap to change ringer mode</string>
@@ -1576,6 +1576,18 @@
 
     <string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring (<xliff:g id="volume level" example="56">%1$s</xliff:g>)</string>
 
+    <!-- An audible a11y label for a button, that opens settings when clicked [CHAR_LIMIT=NONE] -->
+    <string name="volume_panel_enter_media_output_settings">Enter output settings</string>
+    <!-- An audible a11y state description for a button, that expands volume sliders menu [CHAR_LIMIT=NONE] -->
+    <string name="volume_panel_expanded_sliders">Volume sliders expanded</string>
+    <!-- An audible a11y state description for a button, that collapses volume sliders menu [CHAR LIMIT=NONE] -->
+    <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string>
+
+    <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] -->
+    <string name="volume_panel_hint_mute">mute %s</string>
+    <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] -->
+    <string name="volume_panel_hint_unmute">unmute %s</string>
+
     <!-- Title with application label for media output settings. [CHAR LIMIT=20] -->
     <string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index c08b083..69aa909 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -77,7 +77,7 @@
     // settings is expanded.
     public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11;
     // Winscope tracing is enabled
-    public static final int SYSUI_STATE_TRACING_ENABLED = 1 << 12;
+    public static final int SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1 << 12;
     // The Assistant gesture should be constrained. It is up to the launcher implementation to
     // decide how to constrain it
     public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13;
@@ -148,7 +148,7 @@
             SYSUI_STATE_OVERVIEW_DISABLED,
             SYSUI_STATE_HOME_DISABLED,
             SYSUI_STATE_SEARCH_DISABLED,
-            SYSUI_STATE_TRACING_ENABLED,
+            SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
             SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
             SYSUI_STATE_BUBBLES_EXPANDED,
             SYSUI_STATE_DIALOG_SHOWING,
@@ -211,8 +211,8 @@
         if ((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0) {
             str.add("a11y_long_click");
         }
-        if ((flags & SYSUI_STATE_TRACING_ENABLED) != 0) {
-            str.add("tracing");
+        if ((flags & SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION) != 0) {
+            str.add("disable_gesture_split_invocation");
         }
         if ((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0) {
             str.add("asst_gesture_constrain");
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c0ae4a1..7f9ae5e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -250,6 +250,10 @@
     @Override
     protected void onViewAttached() {
         mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+        if (migrateClocksToBlueprint()) {
+            return;
+        }
+
         mStatusArea.addOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
         mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
         mConfigurationController.addCallback(mConfigurationListener);
@@ -257,6 +261,10 @@
 
     @Override
     protected void onViewDetached() {
+        if (migrateClocksToBlueprint()) {
+            return;
+        }
+
         mStatusArea.removeOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
         mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
         mConfigurationController.removeCallback(mConfigurationListener);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
index 0ef3d20..a90d4b2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -118,6 +118,12 @@
                 iconResId = R.drawable.pip_ic_close_white
             )
         )
+
+        // Ensure this is unfocusable & uninteractable
+        isClickable = false
+        isFocusable = false
+        importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
+
         // END DragToInteractView modification
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index d3e85e0..1f04599 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -113,13 +113,8 @@
 
     /* Moves position without updating underlying percentage position. Can be animated. */
     void moveToPosition(PointF position, boolean animateMovement) {
-        if (Flags.floatingMenuImeDisplacementAnimation()) {
-            moveToPositionX(position.x, animateMovement);
-            moveToPositionY(position.y, animateMovement);
-        } else {
-            moveToPositionX(position.x, /* animateMovement = */ false);
-            moveToPositionY(position.y, /* animateMovement = */ false);
-        }
+        moveToPositionX(position.x, animateMovement);
+        moveToPositionY(position.y, animateMovement);
     }
 
     void moveToPositionX(float positionX) {
@@ -127,7 +122,7 @@
     }
 
     void moveToPositionX(float positionX, boolean animateMovement) {
-        if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+        if (animateMovement) {
             springMenuWith(DynamicAnimation.TRANSLATION_X,
                     createSpringForce(),
                     /* velocity = */ 0,
@@ -142,7 +137,7 @@
     }
 
     void moveToPositionY(float positionY, boolean animateMovement) {
-        if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+        if (animateMovement) {
             springMenuWith(DynamicAnimation.TRANSLATION_Y,
                     createSpringForce(),
                     /* velocity = */ 0,
@@ -455,7 +450,7 @@
                 ? MIN_PERCENT
                 : Math.min(MAX_PERCENT, position.y / draggableBounds.height());
 
-        if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) {
+        if (!writeToPosition) {
             mMenuView.onEdgeChangedIfNeeded();
         } else {
             mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index e57323b..35fe6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -74,6 +74,12 @@
         addView(mTextView, Index.TEXT_VIEW,
                 new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
         addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+
+        // The message box is not focusable, but will announce its contents when it appears.
+        // The textView and button are still interactable.
+        setClickable(false);
+        setFocusable(false);
+        setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 577bbc0..0c9712d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -21,17 +21,10 @@
 import android.annotation.SuppressLint;
 import android.content.ComponentCallbacks;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.SettingsStringUtil;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
@@ -101,6 +94,10 @@
         loadLayoutResources();
 
         addView(mTargetFeaturesView);
+
+        setClickable(false);
+        setFocusable(false);
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
     @Override
@@ -224,8 +221,7 @@
         }
 
         // We can skip animating if FAB is not visible
-        if (Flags.floatingMenuImeDisplacementAnimation()
-                && animateMovement && getVisibility() == VISIBLE) {
+        if (animateMovement && getVisibility() == VISIBLE) {
             mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true);
             // onArrivalAtPosition() is called at the end of the animation.
         } else {
@@ -331,7 +327,7 @@
             mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
         }
 
-        if (Flags.floatingMenuOverlapsNavBarsFlag() && !Flags.floatingMenuAnimatedTuck()) {
+        if (!Flags.floatingMenuAnimatedTuck()) {
             if (isMoveToTucked) {
                 final float halfWidth = getMenuWidth() / 2.0f;
                 final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
@@ -431,22 +427,6 @@
         onPositionChanged();
     }
 
-    void gotoEditScreen() {
-        if (!Flags.floatingMenuDragToEdit()) {
-            return;
-        }
-        mMenuAnimationController.flingMenuThenSpringToEdge(
-                getMenuPosition().x, 100f, 0f);
-
-        Intent intent = getIntentForEditScreen();
-        PackageManager packageManager = getContext().getPackageManager();
-        List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
-                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
-        if (!activities.isEmpty()) {
-            mContext.startActivity(intent);
-        }
-    }
-
     void incrementTexMetricForAllTargets(String metric) {
         if (!Flags.floatingMenuDragToEdit()) {
             return;
@@ -461,23 +441,6 @@
         Counter.logIncrementWithUid(metric, uid);
     }
 
-    Intent getIntentForEditScreen() {
-        List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
-                mSecureSettings.getStringForUser(
-                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
-                        UserHandle.USER_CURRENT)).stream().toList();
-
-        Intent intent = new Intent(
-                Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
-        Bundle args = new Bundle();
-        Bundle fragmentArgs = new Bundle();
-        fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
-        args.putBundle(":settings:show_fragment_args", fragmentArgs);
-        intent.replaceExtras(args);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        return intent;
-    }
-
     private InstantInsetLayerDrawable getContainerViewInsetLayer() {
         return (InstantInsetLayerDrawable) getBackground();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 4865fce..760e1c3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -34,7 +34,6 @@
 
 import androidx.annotation.DimenRes;
 
-import com.android.systemui.Flags;
 import com.android.systemui.res.R;
 
 import java.lang.annotation.Retention;
@@ -155,11 +154,6 @@
         final int margin = getMenuMargin();
         final Rect draggableBounds = new Rect(getWindowAvailableBounds());
 
-        if (!Flags.floatingMenuOverlapsNavBarsFlag()) {
-            // Initializes start position for mapping the translation of the menu view.
-            draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0);
-        }
-
         draggableBounds.top += margin;
         draggableBounds.right -= getMenuWidth();
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index cd3b8a6..85bf784 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -36,19 +36,24 @@
 import android.annotation.StringDef;
 import android.annotation.SuppressLint;
 import android.app.NotificationManager;
+import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.SettingsStringUtil;
 import android.util.ArraySet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -120,6 +125,7 @@
     private final MenuAnimationController mMenuAnimationController;
     private final AccessibilityManager mAccessibilityManager;
     private final NotificationManager mNotificationManager;
+    private StatusBarManager mStatusBarManager;
     private final MenuNotificationFactory mNotificationFactory;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final IAccessibilityFloatingMenu mFloatingMenu;
@@ -246,6 +252,7 @@
         mDismissView.getCircle().setId(R.id.action_remove_menu);
         mNotificationFactory = new MenuNotificationFactory(context);
         mNotificationManager = context.getSystemService(NotificationManager.class);
+        mStatusBarManager = context.getSystemService(StatusBarManager.class);
 
         if (Flags.floatingMenuDragToEdit()) {
             mDragToInteractAnimationController = new DragToInteractAnimationController(
@@ -319,6 +326,9 @@
         if (Flags.floatingMenuAnimatedTuck()) {
             setClipChildren(true);
         }
+        setClickable(false);
+        setFocusable(false);
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
     @Override
@@ -443,21 +453,18 @@
     }
 
     public void onMoveToTuckedChanged(boolean moveToTuck) {
-        if (Flags.floatingMenuOverlapsNavBarsFlag()) {
-            if (moveToTuck) {
-                final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
-                final int[] location = getLocationOnScreen();
-                bounds.offset(
-                        location[0],
-                        location[1]
-                );
+        if (moveToTuck) {
+            final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
+            final int[] location = getLocationOnScreen();
+            bounds.offset(
+                    location[0],
+                    location[1]
+            );
 
-                setClipBounds(bounds);
-            }
-            // Instead of clearing clip bounds when moveToTuck is false,
-            // wait until the spring animation finishes.
+            setClipBounds(bounds);
         }
-        // Function is a no-operation if flag is disabled.
+        // Instead of clearing clip bounds when moveToTuck is false,
+        // wait until the spring animation finishes.
     }
 
     private void onSpringAnimationsEndAction() {
@@ -475,9 +482,7 @@
                 setClipBounds(null);
             }
         }
-        if (Flags.floatingMenuImeDisplacementAnimation()) {
-            mMenuView.onArrivalAtPosition(false);
-        }
+        mMenuView.onArrivalAtPosition(false);
     }
 
     void dispatchAccessibilityAction(int id) {
@@ -490,7 +495,7 @@
             mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_DISMISS);
         } else if (id == R.id.action_edit
                 && Flags.floatingMenuDragToEdit()) {
-            mMenuView.gotoEditScreen();
+            gotoEditScreen();
             mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_EDIT);
         }
         mDismissView.hide();
@@ -499,6 +504,40 @@
                 id, /* scaleUp= */ false);
     }
 
+    void gotoEditScreen() {
+        if (!Flags.floatingMenuDragToEdit()) {
+            return;
+        }
+        mMenuAnimationController.flingMenuThenSpringToEdge(
+                mMenuView.getMenuPosition().x, 100f, 0f);
+
+        Intent intent = getIntentForEditScreen();
+        PackageManager packageManager = getContext().getPackageManager();
+        List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
+        if (!activities.isEmpty()) {
+            mContext.startActivity(intent);
+            mStatusBarManager.collapsePanels();
+        }
+    }
+
+    Intent getIntentForEditScreen() {
+        List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
+                mSecureSettings.getStringForUser(
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                        UserHandle.USER_CURRENT)).stream().toList();
+
+        Intent intent = new Intent(
+                Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+        Bundle args = new Bundle();
+        Bundle fragmentArgs = new Bundle();
+        fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
+        args.putBundle(":settings:show_fragment_args", fragmentArgs);
+        intent.replaceExtras(args);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        return intent;
+    }
+
     private CharSequence getMigrationMessage() {
         final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index bc9d1ff..6b1240b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -20,11 +20,9 @@
 
 import android.content.Context;
 import android.graphics.PixelFormat;
-import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
-import com.android.systemui.Flags;
 import com.android.systemui.util.settings.SecureSettings;
 
 /**
@@ -88,14 +86,9 @@
         params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
         params.windowAnimations = android.R.style.Animation_Translucent;
         // Insets are configured to allow the menu to display over navigation and system bars.
-        if (Flags.floatingMenuOverlapsNavBarsFlag()) {
-            params.setFitInsetsTypes(0);
-            params.layoutInDisplayCutoutMode =
-                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        } else {
-            params.setFitInsetsTypes(
-                    WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
-        }
+        params.setFitInsetsTypes(0);
+        params.layoutInDisplayCutoutMode =
+                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         return params;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index af32eb5..000f03a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -109,7 +109,8 @@
             biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
             !keyguardUpdateMonitor.isFingerprintLockedOut &&
             !keyguardStateController.isUnlocked &&
-            !statusBarStateController.isDozing
+            !statusBarStateController.isDozing &&
+            !bouncerRepository.primaryBouncerShow.value
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 8c87b0c..893887f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -93,6 +93,8 @@
     val keyguardAuthenticatedPrimaryAuth: Flow<Int> = repository.keyguardAuthenticatedPrimaryAuth
     val keyguardAuthenticatedBiometrics: Flow<Boolean> =
         repository.keyguardAuthenticatedBiometrics.filterNotNull()
+    val keyguardAuthenticatedBiometricsHandled: Flow<Unit> =
+        repository.keyguardAuthenticatedBiometrics.filter { it == null }.map {}
     val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
         repository.userRequestedBouncerWhenAlreadyAuthenticated.filterNotNull()
     val isShowing: StateFlow<Boolean> = repository.primaryBouncerShow
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
index 1c9d1f0..e1fea5f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -23,12 +23,14 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.bouncer.ui.BouncerViewDelegate
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
 /** Models UI state for the lock screen bouncer; handles user input. */
+@ExperimentalCoroutinesApi
 class KeyguardBouncerViewModel
 @Inject
 constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index c3c7411..ef686f9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal
 
+import android.provider.Settings
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.CoreStartable
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -24,25 +25,32 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.SystemSettings
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
 
 /**
  * A [CoreStartable] responsible for automatically navigating between communal scenes when certain
  * conditions are met.
  */
-@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalSceneStartable
 @Inject
@@ -50,9 +58,13 @@
     private val dockManager: DockManager,
     private val communalInteractor: CommunalInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val systemSettings: SystemSettings,
     @Application private val applicationScope: CoroutineScope,
     @Background private val bgScope: CoroutineScope,
 ) : CoreStartable {
+    private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
+
     override fun start() {
         // Handle automatically switching based on keyguard state.
         keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -78,6 +90,49 @@
         //                }
         //            }
         //            .launchIn(bgScope)
+
+        systemSettings
+            .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
+            // Read the setting value on start.
+            .emitOnStart()
+            .onEach {
+                screenTimeout =
+                    systemSettings.getInt(
+                        Settings.System.SCREEN_OFF_TIMEOUT,
+                        DEFAULT_SCREEN_TIMEOUT
+                    )
+            }
+            .launchIn(bgScope)
+
+        // Handle timing out back to the dream.
+        bgScope.launch {
+            combine(
+                    communalInteractor.desiredScene,
+                    // Emit a value on start so the combine starts.
+                    communalInteractor.userActivity.emitOnStart()
+                ) { scene, _ ->
+                    // Time out should run whenever we're dreaming and the hub is open, even if not
+                    // docked.
+                    scene == CommunalScenes.Communal
+                }
+                // mapLatest cancels the previous action block when new values arrive, so any
+                // already running timeout gets cancelled when conditions change or user interaction
+                // is detected.
+                .mapLatest { shouldTimeout ->
+                    if (!shouldTimeout) {
+                        return@mapLatest false
+                    }
+
+                    delay(screenTimeout.milliseconds)
+                    true
+                }
+                .sample(keyguardInteractor.isDreaming, ::Pair)
+                .collect { (shouldTimeout, isDreaming) ->
+                    if (isDreaming && shouldTimeout) {
+                        communalInteractor.onSceneChanged(CommunalScenes.Blank)
+                    }
+                }
+        }
     }
 
     private suspend fun determineSceneAfterTransition(
@@ -105,5 +160,6 @@
     companion object {
         val AWAKE_DEBOUNCE_DELAY = 5.seconds
         val DOCK_DEBOUNCE_DELAY = 1.seconds
+        val DEFAULT_SCREEN_TIMEOUT = 15000
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 940b48c..52025b1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -63,10 +63,13 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -84,7 +87,7 @@
 class CommunalInteractor
 @Inject
 constructor(
-    @Application applicationScope: CoroutineScope,
+    @Application val applicationScope: CoroutineScope,
     broadcastDispatcher: BroadcastDispatcher,
     private val communalRepository: CommunalRepository,
     private val widgetRepository: CommunalWidgetRepository,
@@ -152,6 +155,14 @@
     /** Transition state of the hub mode. */
     val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState
 
+    val _userActivity: MutableSharedFlow<Unit> =
+        MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+    val userActivity: Flow<Unit> = _userActivity.asSharedFlow()
+
+    fun signalUserInteraction() {
+        _userActivity.tryEmit(Unit)
+    }
+
     /**
      * Updates the transition state of the hub [SceneTransitionLayout].
      *
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 85f3c20..c913300 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -45,6 +45,10 @@
     val selectedKey: StateFlow<String?>
         get() = _selectedKey
 
+    fun signalUserInteraction() {
+        communalInteractor.signalUserInteraction()
+    }
+
     fun onSceneChanged(scene: SceneKey) {
         communalInteractor.onSceneChanged(scene)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 0e487d2..9c68c45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -159,6 +159,11 @@
         lastAnimator?.cancel()
         lastAnimator = info.animator
 
+        // Cancel any existing manual transitions
+        updateTransitionId?.let { uuid ->
+            updateTransition(uuid, lastStep.value, TransitionState.CANCELED)
+        }
+
         info.animator?.let { animator ->
             // An animator was provided, so use it to run the transition
             animator.setFloatValues(startingValue, 1f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 143edf9..7734973 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -57,6 +57,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
@@ -179,11 +180,19 @@
 
     /** Keyguard can be clipped at the top as the shade is dragged */
     val topClippingBounds: Flow<Int?> =
-        combine(configurationInteractor.onAnyConfigurationChange, repository.topClippingBounds) {
-            _,
-            topClippingBounds ->
-            topClippingBounds
-        }
+        combineTransform(
+                configurationInteractor.onAnyConfigurationChange,
+                keyguardTransitionInteractor
+                    .transitionValue(GONE)
+                    .map { it == 1f }
+                    .onStart { emit(false) },
+                repository.topClippingBounds
+            ) { _, isGone, topClippingBounds ->
+                if (!isGone) {
+                    emit(topClippingBounds)
+                }
+            }
+            .distinctUntilChanged()
 
     /** Last point that [KeyguardRootView] view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index c28e49d..68ea5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -34,7 +34,7 @@
 class KeyguardTransitionAuditLogger
 @Inject
 constructor(
-    @Application private val scope: CoroutineScope,
+    @Background private val scope: CoroutineScope,
     private val interactor: KeyguardTransitionInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val logger: KeyguardLogger,
@@ -70,6 +70,12 @@
         }
 
         scope.launch {
+            sharedNotificationContainerViewModel.bounds.collect {
+                logger.log(TAG, VERBOSE, "Notif: bounds", it)
+            }
+        }
+
+        scope.launch {
             sharedNotificationContainerViewModel.isOnLockscreenWithoutShade.collect {
                 logger.log(TAG, VERBOSE, "Notif: isOnLockscreenWithoutShade", it)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 00902b4..e6655ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -38,9 +39,12 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -50,6 +54,7 @@
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Encapsulates business-logic related to the keyguard transitions. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -58,6 +63,7 @@
 @Inject
 constructor(
     @Application val scope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
     private val keyguardRepository: KeyguardRepository,
     private val repository: KeyguardTransitionRepository,
     private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
@@ -70,6 +76,30 @@
 ) {
     private val TAG = this::class.simpleName
 
+    private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>()
+
+    /**
+     * Numerous flows are derived from, or care directly about, the transition value in and out of a
+     * single state. This prevent the redundant filters from running.
+     */
+    private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> {
+        return transitionValueCache.getOrPut(state) {
+            MutableSharedFlow<Float>(
+                extraBufferCapacity = 2,
+                onBufferOverflow = BufferOverflow.DROP_OLDEST
+            )
+        }
+    }
+
+    init {
+        scope.launch(mainDispatcher) {
+            repository.transitions.collect { step ->
+                getTransitionValueFlow(step.from).emit(1f - step.value)
+                getTransitionValueFlow(step.to).emit(step.value)
+            }
+        }
+    }
+
     /** (any)->GONE transition information */
     val anyStateToGoneTransition: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.to == GONE }
@@ -353,15 +383,7 @@
     fun transitionValue(
         state: KeyguardState,
     ): Flow<Float> {
-        return repository.transitions
-            .filter { it.from == state || it.to == state }
-            .map {
-                if (it.from == state) {
-                    1 - it.value
-                } else {
-                    it.value
-                }
-            }
+        return getTransitionValueFlow(state)
     }
 
     fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
index 02d1471..0c8ddee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
@@ -20,6 +20,6 @@
 data class BurnInModel(
     val translationX: Int = 0,
     val translationY: Int = 0,
-    val scale: Float = 0f,
+    val scale: Float = 1f,
     val scaleClockOnly: Boolean = false,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index d400210..3a2781c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -102,9 +102,16 @@
 
                 launch {
                     viewModel.scrimAlpha.collect {
+                        val wasVisible = alternateBouncerViewContainer.visibility == View.VISIBLE
                         alternateBouncerViewContainer.visibility =
                             if (it < .1f) View.INVISIBLE else View.VISIBLE
                         scrim.viewAlpha = it
+                        if (
+                            wasVisible && alternateBouncerViewContainer.visibility == View.INVISIBLE
+                        ) {
+                            // view is no longer visible
+                            viewModel.hideAlternateBouncer()
+                        }
                     }
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index 1abf4a6..f20c4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -21,8 +21,10 @@
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -73,13 +75,17 @@
 
     @Binds
     @IntoSet
+    abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
     abstract fun aodToLockscreen(
         impl: AodToLockscreenTransitionViewModel
     ): DeviceEntryIconTransition
 
     @Binds
     @IntoSet
-    abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition
+    abstract fun aodToOccluded(impl: AodToOccludedTransitionViewModel): DeviceEntryIconTransition
 
     @Binds
     @IntoSet
@@ -93,6 +99,12 @@
 
     @Binds
     @IntoSet
+    abstract fun dozingToOccluded(
+        impl: DozingToOccludedTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
     abstract fun dozingToPrimaryBouncer(
         impl: DozingToPrimaryBouncerTransitionViewModel
     ): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 2526f0a..10a9e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -89,4 +89,8 @@
     fun showPrimaryBouncer() {
         statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
     }
+
+    fun hideAlternateBouncer() {
+        statusBarKeyguardViewManager.hideAlternateBouncer(false)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index abf2372..6042117 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -72,7 +72,11 @@
                     duration = TO_LOCKSCREEN_DURATION,
                     onStep = { value -> -translatePx + value * translatePx },
                     interpolator = EMPHASIZED,
-                    onCancel = { -translatePx.toFloat() },
+                    // Move notifications back to their original position since they can be
+                    // accessed from the shade, and also keyguard elements in case the animation
+                    // is cancelled.
+                    onFinish = { 0f },
+                    onCancel = { 0f },
                     name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX"
                 )
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index b7f7b06..5cbc1d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -71,7 +71,8 @@
                     duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
                     onStep = { value -> value * translatePx },
                     // Move notifications back to their original position since they can be
-                    // accessed from the shade.
+                    // accessed from the shade, and also keyguard elements in case the animation
+                    // is cancelled.
                     onFinish = { 0f },
                     onCancel = { 0f },
                     interpolator = EMPHASIZED,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index bd66843..d82b175 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.recordissue.IssueRecordingService
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
@@ -66,6 +67,7 @@
     private val keyguardDismissUtil: KeyguardDismissUtil,
     private val keyguardStateController: KeyguardStateController,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val panelInteractor: PanelInteractor,
     private val userContextProvider: UserContextProvider,
     private val delegateFactory: RecordIssueDialogDelegate.Factory,
 ) :
@@ -138,6 +140,8 @@
                 .create {
                     isRecording = true
                     startIssueRecordingService(it.screenRecord, it.winscopeTracing)
+                    dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+                    panelInteractor.collapsePanels()
                     refreshState()
                 }
                 .createDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
index dcae088..1247854 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
@@ -30,7 +30,7 @@
     private val bluetoothAutoOnRepository: BluetoothAutoOnRepository,
 ) {
 
-    val isEnabled = bluetoothAutoOnRepository.getValue.map { it == ENABLED }.distinctUntilChanged()
+    val isEnabled = bluetoothAutoOnRepository.isAutoOn.map { it == ENABLED }.distinctUntilChanged()
 
     /**
      * Checks if the auto on value is present in the repository.
@@ -49,7 +49,7 @@
             Log.e(TAG, "Trying to set toggle value while feature not available.")
         } else {
             val newValue = if (value) ENABLED else DISABLED
-            bluetoothAutoOnRepository.setValue(newValue)
+            bluetoothAutoOnRepository.setAutoOn(newValue)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
index e17b4d3..f97fc38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.qs.tiles.dialog.bluetooth
 
-import android.os.UserHandle
-import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -27,17 +25,21 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
-/** Repository class responsible for managing the Bluetooth Auto-On feature settings. */
-// TODO(b/316822488): Handle multi-user
+/**
+ * Repository class responsible for managing the Bluetooth Auto-On feature settings for the current
+ * user.
+ */
 @SysUISingleton
 class BluetoothAutoOnRepository
 @Inject
@@ -47,21 +49,24 @@
     @Application private val coroutineScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
-    // Flow representing the auto on setting value
-    internal val getValue: Flow<Int> =
-        secureSettings
-            .observerFlow(UserHandle.USER_SYSTEM, SETTING_NAME)
-            .onStart { emit(Unit) }
-            .map {
-                if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
-                    Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
-                    return@map UNSET
-                }
-                secureSettings.getIntForUser(SETTING_NAME, UNSET, UserHandle.USER_SYSTEM)
+
+    // Flow representing the auto on setting value for the current user
+    @OptIn(ExperimentalCoroutinesApi::class)
+    internal val isAutoOn: StateFlow<Int> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { userInfo ->
+                secureSettings
+                    .observerFlow(userInfo.id, SETTING_NAME)
+                    .onStart { emit(Unit) }
+                    .map { secureSettings.getIntForUser(SETTING_NAME, UNSET, userInfo.id) }
             }
             .distinctUntilChanged()
             .flowOn(backgroundDispatcher)
-            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(replayExpirationMillis = 0))
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
+                UNSET
+            )
 
     /**
      * Checks if the auto on setting value is ever set for the current user.
@@ -70,12 +75,11 @@
      */
     suspend fun isValuePresent(): Boolean =
         withContext(backgroundDispatcher) {
-            if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
-                Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
-                false
-            } else {
-                secureSettings.getIntForUser(SETTING_NAME, UNSET, UserHandle.USER_SYSTEM) != UNSET
-            }
+            secureSettings.getIntForUser(
+                SETTING_NAME,
+                UNSET,
+                userRepository.getSelectedUserInfo().id
+            ) != UNSET
         }
 
     /**
@@ -83,18 +87,17 @@
      *
      * @param value The new setting value to be applied.
      */
-    suspend fun setValue(value: Int) {
+    suspend fun setAutoOn(value: Int) {
         withContext(backgroundDispatcher) {
-            if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
-                Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
-            } else {
-                secureSettings.putIntForUser(SETTING_NAME, value, UserHandle.USER_SYSTEM)
-            }
+            secureSettings.putIntForUser(
+                SETTING_NAME,
+                value,
+                userRepository.getSelectedUserInfo().id
+            )
         }
     }
 
     companion object {
-        private const val TAG = "BluetoothAutoOnRepository"
         const val SETTING_NAME = "bluetooth_automatic_turn_on"
         const val UNSET = -1
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index d0ff338..9ae049d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -37,7 +37,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
 
@@ -115,8 +114,6 @@
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInterface;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -128,6 +125,8 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import dagger.Lazy;
+
 /**
  * Class to send information from overview to launcher with a binder.
  */
@@ -670,8 +669,7 @@
             // Listen for tracing state changes
             @Override
             public void onTracingStateChanged(boolean enabled) {
-                mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
-                        .commitUpdate(mContext.getDisplayId());
+                // TODO(b/286509643) Cleanup callers of this; Unused downstream
             }
 
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 8a84496..7009816 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -27,8 +27,10 @@
 import android.util.Log
 import androidx.core.content.FileProvider
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.dagger.qualifiers.LongRunning
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.RecordingController
 import com.android.systemui.screenrecord.RecordingService
@@ -54,7 +56,9 @@
     uiEventLogger: UiEventLogger,
     notificationManager: NotificationManager,
     userContextProvider: UserContextProvider,
-    keyguardDismissUtil: KeyguardDismissUtil
+    keyguardDismissUtil: KeyguardDismissUtil,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
+    private val panelInteractor: PanelInteractor,
 ) :
     RecordingService(
         controller,
@@ -102,6 +106,8 @@
             }
             ACTION_SHARE -> {
                 shareRecording(intent)
+                dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+                panelInteractor.collapsePanels()
 
                 // Unlike all other actions, action_share has different behavior for the screen
                 // recording qs tile than it does for the record issue qs tile. Return sticky to
@@ -124,13 +130,11 @@
             FileSender.buildSendIntent(this, listOf(sharableUri))
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
-        if (mNotificationId != NOTIF_BASE_ID) {
-            mNotificationManager.cancelAsUser(
-                null,
-                mNotificationId,
-                UserHandle(mUserContextTracker.userContext.userId)
-            )
-        }
+        mNotificationManager.cancelAsUser(
+            null,
+            mNotificationId,
+            UserHandle(mUserContextTracker.userContext.userId)
+        )
 
         // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
         mKeyguardDismissUtil.executeWhenUnlocked(
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index ff18a11..7313a49 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -28,8 +28,8 @@
 import android.widget.Button
 import android.widget.PopupMenu
 import android.widget.Switch
-import androidx.annotation.AnyThread
 import androidx.annotation.MainThread
+import androidx.annotation.WorkerThread
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -74,6 +74,7 @@
 
     @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
     private lateinit var issueTypeButton: Button
+    private var hasSelectedIssueType: Boolean = false
 
     @MainThread
     override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
@@ -82,15 +83,21 @@
             setTitle(context.getString(R.string.qs_record_issue_label))
             setIcon(R.drawable.qs_record_issue_icon_off)
             setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
-            setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
-                onStarted.accept(
-                    IssueRecordingConfig(
-                        screenRecordSwitch.isChecked,
-                        true /* TODO: Base this on issueType selected */
-                    )
-                )
-                dismiss()
-            }
+            setPositiveButton(
+                R.string.qs_record_issue_start,
+                { _, _ ->
+                    if (hasSelectedIssueType) {
+                        onStarted.accept(
+                            IssueRecordingConfig(
+                                screenRecordSwitch.isChecked,
+                                true /* TODO: Base this on issueType selected */
+                            )
+                        )
+                        dismiss()
+                    }
+                },
+                false
+            )
         }
     }
 
@@ -104,49 +111,47 @@
 
             screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
             screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled ->
-                onScreenRecordSwitchClicked(context, isEnabled)
+                if (isEnabled) {
+                    bgExecutor.execute { onScreenRecordSwitchClicked() }
+                }
             }
             issueTypeButton = requireViewById(R.id.issue_type_button)
             issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
         }
     }
 
-    @AnyThread
-    private fun onScreenRecordSwitchClicked(context: Context, isEnabled: Boolean) {
-        if (!isEnabled) return
-
-        bgExecutor.execute {
-            if (
-                flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
-                    devicePolicyResolver
-                        .get()
-                        .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
-            ) {
-                mainExecutor.execute {
-                    screenCaptureDisabledDialogDelegate.createDialog().show()
-                    screenRecordSwitch.isChecked = false
-                }
-                return@execute
+    @WorkerThread
+    private fun onScreenRecordSwitchClicked() {
+        if (
+            flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
+                devicePolicyResolver
+                    .get()
+                    .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
+        ) {
+            mainExecutor.execute {
+                screenCaptureDisabledDialogDelegate.createDialog().show()
+                screenRecordSwitch.isChecked = false
             }
+            return
+        }
 
-            mediaProjectionMetricsLogger.notifyProjectionInitiated(
-                userTracker.userId,
-                SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
-            )
+        mediaProjectionMetricsLogger.notifyProjectionInitiated(
+            userTracker.userId,
+            SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+        )
 
-            if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
-                val prefs =
-                    userFileManager.getSharedPreferences(
-                        RecordIssueTile.TILE_SPEC,
-                        Context.MODE_PRIVATE,
-                        userTracker.userId
-                    )
-                if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
-                    mainExecutor.execute {
-                        ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
-                            setOnCancelListener { screenRecordSwitch.isChecked = false }
-                            show()
-                        }
+        if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
+            val prefs =
+                userFileManager.getSharedPreferences(
+                    RecordIssueTile.TILE_SPEC,
+                    Context.MODE_PRIVATE,
+                    userTracker.userId
+                )
+            if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
+                mainExecutor.execute {
+                    ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
+                        setOnCancelListener { screenRecordSwitch.isChecked = false }
+                        show()
                     }
                 }
             }
@@ -174,5 +179,6 @@
             setForceShowIcon(true)
             show()
         }
+        hasSelectedIssueType = true
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index a343ded..8197b66 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -37,9 +37,6 @@
      */
     val isPanelExpanded: Boolean
 
-    /** Returns whether shade's height is zero. */
-    val isFullyCollapsed: Boolean
-
     /** Returns whether the shade is tracking touches for expand/collapse of the shade or QS. */
     val isTracking: Boolean
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index c9140b5..48a2d75 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -39,6 +39,7 @@
     override fun animateCollapseQs(fullyCollapse: Boolean) {}
     override fun canBeCollapsed(): Boolean = false
     @Deprecated("Use ShadeAnimationInteractor instead") override val isCollapsing: Boolean = false
+    @Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
     override val isFullyCollapsed: Boolean = false
     override val isTracking: Boolean = false
     override val isViewEnabled: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
index bd96a33..79ffe06 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
@@ -55,6 +55,9 @@
     )
     val isFullyExpanded: Boolean
 
+    /** Returns whether shade's height is zero. */
+    @Deprecated("Use !ShadeInteractor.isAnyExpanded instead") val isFullyCollapsed: Boolean
+
     /** Returns whether the shade is in the process of collapsing. */
     @Deprecated("Use ShadeAnimationInteractor instead") val isCollapsing: Boolean
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index c5a6968..3ad2b56 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -101,6 +101,9 @@
     )
     override val isFullyExpanded = shadeInteractor.isAnyFullyExpanded.value
 
+    @Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
+    override val isFullyCollapsed = !shadeInteractor.isAnyExpanded.value
+
     @Deprecated("Use ShadeAnimationInteractor instead")
     override val isCollapsing =
         shadeAnimationInteractor.isAnyCloseAnimationRunning.value ||
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 6414af3..421a761 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -46,7 +46,10 @@
     sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
     repository: ShadeRepository,
 ) : BaseShadeInteractor {
-    /** The amount [0-1] that the shade has been opened */
+    /**
+     * The amount [0-1] that the shade has been opened. Uses stateIn to avoid redundant calculations
+     * in downstream flows.
+     */
     override val shadeExpansion: Flow<Float> =
         combine(
                 repository.lockscreenShadeExpansion,
@@ -71,6 +74,7 @@
                 }
             }
             .distinctUntilChanged()
+            .stateIn(scope, SharingStarted.Eagerly, 0f)
 
     override val qsExpansion: StateFlow<Float> = repository.qsExpansion
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
index 2707ed8..b77748e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_OCCLUDED
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_SHOWING
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_LOCKED_SHADE
+import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_USER_SETUP_INCOMPLETE
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_EXPECTED_TO_HUN
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NOT_IMPORTANT_ENOUGH
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NO_FULL_SCREEN_INTENT
@@ -101,6 +102,7 @@
         FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"),
         FSI_LOCKED_SHADE(true, "locked shade"),
         FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"),
+        FSI_USER_SETUP_INCOMPLETE(true, "user setup incomplete"),
         NO_FSI_NO_HUN_OR_KEYGUARD(
             false,
             "no HUN or keyguard",
@@ -189,6 +191,10 @@
             return FSI_DEVICE_NOT_PROVISIONED
         }
 
+        if (!deviceProvisionedController.isCurrentUserSetup) {
+            return FSI_USER_SETUP_INCOMPLETE
+        }
+
         return NO_FSI_NO_HUN_OR_KEYGUARD
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index b0155f1..c084482 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -104,7 +104,11 @@
         /**
          * The device is not provisioned, launch FSI.
          */
-        FSI_NOT_PROVISIONED(true);
+        FSI_NOT_PROVISIONED(true),
+        /**
+         * The current user has not completed setup, launch FSI.
+         */
+        FSI_USER_SETUP_INCOMPLETE(true);
 
         public final boolean shouldLaunch;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index dc9eeb3..a655c72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -362,6 +362,12 @@
                     suppressedByDND);
         }
 
+        // The current user hasn't completed setup, launch FSI.
+        if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+            return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_USER_SETUP_INCOMPLETE,
+                    suppressedByDND);
+        }
+
         // Detect the case determined by b/231322873 to launch FSI while device is in use,
         // as blocked by the correct implementation, and report the event.
         return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 77e9425..9479762 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -49,6 +49,7 @@
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.provider.Settings;
 import android.util.AttributeSet;
@@ -208,6 +209,9 @@
     private float mQsExpansionFraction;
     private final int mSplitShadeMinContentHeight;
     private String mLastUpdateSidePaddingDumpString;
+    private long mLastUpdateSidePaddingElapsedRealtime;
+    private String mLastInitViewDumpString;
+    private long mLastInitViewElapsedRealtime;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -887,17 +891,34 @@
         mOverflingDistance = configuration.getScaledOverflingDistance();
 
         Resources res = context.getResources();
+        final boolean isSmallScreenLandscape = res.getBoolean(R.bool.is_small_screen_landscape);
         boolean useSmallLandscapeLockscreenResources = mIsSmallLandscapeLockscreenEnabled
-                && res.getBoolean(R.bool.is_small_screen_landscape);
+                && isSmallScreenLandscape;
         // TODO (b/293252410) remove condition here when flag is launched
         //  Instead update the config_skinnyNotifsInLandscape to be false whenever
         //  is_small_screen_landscape is true. Then, only use the config_skinnyNotifsInLandscape.
+        final boolean configSkinnyNotifsInLandscape = res.getBoolean(
+                R.bool.config_skinnyNotifsInLandscape);
         if (useSmallLandscapeLockscreenResources) {
             mSkinnyNotifsInLandscape = false;
         } else {
-            mSkinnyNotifsInLandscape = res.getBoolean(
-                    R.bool.config_skinnyNotifsInLandscape);
+            mSkinnyNotifsInLandscape = configSkinnyNotifsInLandscape;
         }
+
+        mLastInitViewDumpString =
+                "mIsSmallLandscapeLockscreenEnabled=" + mIsSmallLandscapeLockscreenEnabled
+                        + " isSmallScreenLandscape=" + isSmallScreenLandscape
+                        + " useSmallLandscapeLockscreenResources="
+                        + useSmallLandscapeLockscreenResources
+                        + " skinnyNotifsInLandscape=" + configSkinnyNotifsInLandscape
+                        + " mSkinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape;
+        mLastInitViewElapsedRealtime = SystemClock.elapsedRealtime();
+
+        if (DEBUG_UPDATE_SIDE_PADDING) {
+            Log.v(TAG, "initView @ elapsedRealtime " + mLastInitViewElapsedRealtime + ": "
+                    + mLastInitViewDumpString);
+        }
+
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
         mStackScrollAlgorithm.initView(context);
         mStateAnimator.initView(context);
@@ -925,22 +946,33 @@
         mLastUpdateSidePaddingDumpString = "viewWidth=" + viewWidth
                 + " skinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape
                 + " orientation=" + orientation;
+        mLastUpdateSidePaddingElapsedRealtime = SystemClock.elapsedRealtime();
 
         if (DEBUG_UPDATE_SIDE_PADDING) {
-            Log.v(TAG, "updateSidePadding: " + mLastUpdateSidePaddingDumpString);
+            Log.v(TAG,
+                    "updateSidePadding @ elapsedRealtime " + mLastUpdateSidePaddingElapsedRealtime
+                            + ": " + mLastUpdateSidePaddingDumpString);
         }
 
-        if (viewWidth == 0 || !mSkinnyNotifsInLandscape) {
+        if (viewWidth == 0) {
+            Log.e(TAG, "updateSidePadding: viewWidth is zero");
             mSidePaddings = mMinimumPaddings;
             return;
         }
 
-        // Portrait is easy, just use the dimen for paddings
         if (orientation == Configuration.ORIENTATION_PORTRAIT) {
             mSidePaddings = mMinimumPaddings;
             return;
         }
 
+        if (mShouldUseSplitNotificationShade) {
+            if (mSkinnyNotifsInLandscape) {
+                Log.e(TAG, "updateSidePadding: mSkinnyNotifsInLandscape has betrayed us!");
+            }
+            mSidePaddings = mMinimumPaddings;
+            return;
+        }
+
         final int innerWidth = viewWidth - mMinimumPaddings * 2;
         final int qsTileWidth = (innerWidth - mQsTilePadding * 3) / 4;
         mSidePaddings = mMinimumPaddings + qsTileWidth + mQsTilePadding;
@@ -4884,6 +4916,7 @@
 
     public void dump(PrintWriter pwOriginal, String[] args) {
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
         pw.println("Internal state:");
         DumpUtilsKt.withIncreasedIndent(pw, () -> {
             println(pw, "pulsing", mPulsing);
@@ -4914,7 +4947,17 @@
             println(pw, "minimumPaddings", mMinimumPaddings);
             println(pw, "qsTilePadding", mQsTilePadding);
             println(pw, "sidePaddings", mSidePaddings);
+            println(pw, "elapsedRealtime", elapsedRealtime);
+            println(pw, "lastInitView", mLastInitViewDumpString);
+            println(pw, "lastInitViewElapsedRealtime", mLastInitViewElapsedRealtime);
+            println(pw, "lastInitViewMillisAgo", elapsedRealtime - mLastInitViewElapsedRealtime);
+            println(pw, "shouldUseSplitNotificationShade", mShouldUseSplitNotificationShade);
             println(pw, "lastUpdateSidePadding", mLastUpdateSidePaddingDumpString);
+            println(pw, "lastUpdateSidePaddingElapsedRealtime",
+                    mLastUpdateSidePaddingElapsedRealtime);
+            println(pw, "lastUpdateSidePaddingMillisAgo",
+                    elapsedRealtime - mLastUpdateSidePaddingElapsedRealtime);
+            println(pw, "isSmallLandscapeLockscreenEnabled", mIsSmallLandscapeLockscreenEnabled);
             mNotificationStackSizeCalculator.dump(pw, args);
         });
         pw.println();
@@ -5482,6 +5525,7 @@
             mAmbientState.setUseSplitShade(split);
             updateDismissBehavior();
             updateUseRoundedRectClipping();
+            requestLayout();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index ece7a7f..5b8b91c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
 import android.view.View
 import android.view.WindowInsets
 import androidx.lifecycle.Lifecycle
@@ -118,13 +116,6 @@
                                             "SharedNotificationContainerVB (collapseFadeIn)"
                                         )
                                     }
-                                    addListener(
-                                        object : AnimatorListenerAdapter() {
-                                            override fun onAnimationEnd(animation: Animator) {
-                                                viewModel.setShadeCollapseFadeInComplete(true)
-                                            }
-                                        }
-                                    )
                                     start()
                                 }
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 5e87a7a..2c4813a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -67,7 +67,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -79,6 +78,7 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.transformWhile
 import kotlinx.coroutines.isActive
@@ -124,6 +124,23 @@
         setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
 
     /**
+     * Is either shade/qs expanded? This intentionally does not use the [ShadeInteractor] version,
+     * as the legacy implementation has extra logic that produces incorrect results.
+     */
+    private val isAnyExpanded =
+        combine(
+                shadeInteractor.shadeExpansion.map { it > 0f },
+                shadeInteractor.qsExpansion.map { it > 0f },
+            ) { shadeExpansion, qsExpansion ->
+                shadeExpansion || qsExpansion
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
+    /**
      * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
      * both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
      * before the other.
@@ -131,17 +148,17 @@
     private val isShadeLocked: Flow<Boolean> =
         combine(
                 keyguardInteractor.statusBarState.map { it == SHADE_LOCKED },
-                shadeInteractor.qsExpansion.map { it > 0f },
-                shadeInteractor.shadeExpansion.map { it > 0f },
-            ) { isShadeLocked, isQsExpanded, isShadeExpanded ->
-                isShadeLocked && (isQsExpanded || isShadeExpanded)
+                isAnyExpanded,
+            ) { isShadeLocked, isAnyExpanded ->
+                isShadeLocked && isAnyExpanded
             }
-            .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
             .dumpWhileCollecting("isShadeLocked")
 
-    private val shadeCollapseFadeInComplete =
-        MutableStateFlow(false).dumpValue("shadeCollapseFadeInComplete")
-
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
         interactor.configurationBasedDimensions
             .map {
@@ -176,20 +193,16 @@
             ) { constrainedNotificationState, transitioningToOrFromLockscreen ->
                 constrainedNotificationState || transitioningToOrFromLockscreen
             }
-            .distinctUntilChanged()
+            .shareIn(scope = applicationScope, started = SharingStarted.Eagerly)
             .dumpWhileCollecting("isOnLockscreen")
 
     /** Are we purely on the keyguard without the shade/qs? */
     val isOnLockscreenWithoutShade: Flow<Boolean> =
         combine(
                 isOnLockscreen,
-                // Shade with notifications
-                shadeInteractor.shadeExpansion.map { it > 0f },
-                // Shade without notifications, quick settings only (pull down from very top on
-                // lockscreen)
-                shadeInteractor.qsExpansion.map { it > 0f },
-            ) { isKeyguard, isShadeVisible, qsExpansion ->
-                isKeyguard && !(isShadeVisible || qsExpansion)
+                isAnyExpanded,
+            ) { isKeyguard, isAnyExpanded ->
+                isKeyguard && !isAnyExpanded
             }
             .stateIn(
                 scope = applicationScope,
@@ -219,13 +232,9 @@
     val isOnGlanceableHubWithoutShade: Flow<Boolean> =
         combine(
                 isOnGlanceableHub,
-                // Shade with notifications
-                shadeInteractor.shadeExpansion.map { it > 0f },
-                // Shade without notifications, quick settings only (pull down from very top on
-                // lockscreen)
-                shadeInteractor.qsExpansion.map { it > 0f },
-            ) { isGlanceableHub, isShadeVisible, qsExpansion ->
-                isGlanceableHub && !(isShadeVisible || qsExpansion)
+                isAnyExpanded,
+            ) { isGlanceableHub, isAnyExpanded ->
+                isGlanceableHub && !isAnyExpanded
             }
             .stateIn(
                 scope = applicationScope,
@@ -283,9 +292,6 @@
                     awaitCollapse().collect { doFadeIn ->
                         if (doFadeIn) {
                             emit(true)
-                            // ... and then for the animation to complete
-                            shadeCollapseFadeInComplete.first { it }
-                            shadeCollapseFadeInComplete.value = false
                         }
                     }
                 }
@@ -295,7 +301,7 @@
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = false,
             )
-            .dumpWhileCollecting("shadeCollapseFadeIn")
+            .dumpValue("shadeCollapseFadeIn")
 
     /**
      * The container occupies the entire screen, and must be positioned relative to other elements.
@@ -323,7 +329,7 @@
                     // When QS expansion > 0, it should directly set the top padding so do not
                     // animate it
                     val animate = qsExpansion == 0f && !isInTransitionToAnyState
-                    keyguardInteractor.notificationContainerBounds.value.copy(
+                    bounds.copy(
                         top = top,
                         isAnimated = animate,
                     )
@@ -547,10 +553,6 @@
         interactor.notificationStackChanged()
     }
 
-    fun setShadeCollapseFadeInComplete(complete: Boolean) {
-        shadeCollapseFadeInComplete.value = complete
-    }
-
     data class ConfigurationBasedDimensions(
         val marginStart: Int,
         val marginTop: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index ab9ecab..1faca00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -57,6 +57,7 @@
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -81,6 +82,7 @@
     private final com.android.systemui.shade.ShadeController mShadeController;
     private final CommandQueue mCommandQueue;
     private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final MetricsLogger mMetricsLogger;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -120,6 +122,7 @@
             ShadeController shadeController,
             CommandQueue commandQueue,
             ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             MetricsLogger metricsLogger,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -147,6 +150,7 @@
         mShadeController = shadeController;
         mCommandQueue = commandQueue;
         mShadeViewController = shadeViewController;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
         mMetricsLogger = metricsLogger;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -304,7 +308,7 @@
             mShadeController.animateCollapseShade();
         } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key.getKeyCode()) {
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
-            if (mShadeViewController.isFullyCollapsed()) {
+            if (mPanelExpansionInteractor.isFullyCollapsed()) {
                 if (mVibrateOnOpening) {
                     vibrateOnNavigationKeyDown();
                 }
@@ -371,7 +375,7 @@
                     mStatusBarKeyguardViewManager.reset(true /* hide */);
                 }
                 mCameraLauncherLazy.get().launchCamera(source,
-                        mShadeViewController.isFullyCollapsed());
+                        mPanelExpansionInteractor.isFullyCollapsed());
                 mCentralSurfaces.updateScrimController();
             } else {
                 // We need to defer the camera launch until the screen comes on, since otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a155e94..24be3db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -43,6 +43,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.AnimationStateHandler;
+import com.android.systemui.statusbar.policy.AvalancheController;
 import com.android.systemui.statusbar.policy.BaseHeadsUpManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -124,9 +125,10 @@
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger,
             JavaAdapter javaAdapter,
-            ShadeInteractor shadeInteractor) {
+            ShadeInteractor shadeInteractor,
+            AvalancheController avalancheController) {
         super(context, logger, handler, globalSettings, systemClock, executor,
-                accessibilityManagerWrapper, uiEventLogger);
+                accessibilityManagerWrapper, uiEventLogger, avalancheController);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
         statusBarStateController.addCallback(mStatusBarStateListener);
@@ -279,7 +281,7 @@
         if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
             headsUpEntry.mRemoteInputActive = remoteInputActive;
             if (remoteInputActive) {
-                headsUpEntry.removeAutoRemovalCallbacks("setRemoteInputActive(true)");
+                headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
             } else {
                 headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
             }
@@ -482,7 +484,7 @@
 
             this.mExpanded = expanded;
             if (expanded) {
-                removeAutoRemovalCallbacks("setExpanded(true)");
+                cancelAutoRemovalCallbacks("setExpanded(true)");
             } else {
                 updateEntry(false /* updatePostTime */, "setExpanded(false)");
             }
@@ -495,7 +497,7 @@
 
             mGutsShownPinned = gutsShownPinned;
             if (gutsShownPinned) {
-                removeAutoRemovalCallbacks("setGutsShownPinned(true)");
+                cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
             } else {
                 updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f29ec8f3..92fd90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -58,6 +59,7 @@
     private val statusBarWindowStateController: StatusBarWindowStateController,
     private val shadeController: ShadeController,
     private val shadeViewController: ShadeViewController,
+    private val panelExpansionInteractor: PanelExpansionInteractor,
     private val windowRootView: Provider<WindowRootView>,
     private val shadeLogger: ShadeLogger,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -218,7 +220,7 @@
                     )
                     return true
                 }
-                if (shadeViewController.isFullyCollapsed && event.y < 1f) {
+                if (panelExpansionInteractor.isFullyCollapsed && event.y < 1f) {
                     // b/235889526 Eat events on the top edge of the phone when collapsed
                     shadeLogger.logMotionEvent(event, "top edge touch ignored")
                     return true
@@ -271,6 +273,7 @@
         private val statusBarWindowStateController: StatusBarWindowStateController,
         private val shadeController: ShadeController,
         private val shadeViewController: ShadeViewController,
+        private val panelExpansionInteractor: PanelExpansionInteractor,
         private val windowRootView: Provider<WindowRootView>,
         private val shadeLogger: ShadeLogger,
         private val viewUtil: ViewUtil,
@@ -292,6 +295,7 @@
                 statusBarWindowStateController,
                 shadeController,
                 shadeViewController,
+                panelExpansionInteractor,
                 windowRootView,
                 shadeLogger,
                 statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 235ed25..69dd507 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -38,6 +39,7 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarWindowController mStatusBarWindowController;
     private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final NotificationStackScrollLayoutController mNsslController;
     private final KeyguardBypassController mKeyguardBypassController;
     private final HeadsUpManager mHeadsUpManager;
@@ -49,6 +51,7 @@
             NotificationShadeWindowController notificationShadeWindowController,
             StatusBarWindowController statusBarWindowController,
             ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             NotificationStackScrollLayoutController nsslController,
             KeyguardBypassController keyguardBypassController,
             HeadsUpManager headsUpManager,
@@ -57,6 +60,7 @@
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarWindowController = statusBarWindowController;
         mShadeViewController = shadeViewController;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mNsslController = nsslController;
         mKeyguardBypassController = keyguardBypassController;
         mHeadsUpManager = headsUpManager;
@@ -74,13 +78,13 @@
         if (inPinnedMode) {
             mNotificationShadeWindowController.setHeadsUpShowing(true);
             mStatusBarWindowController.setForceStatusBarVisible(true);
-            if (mShadeViewController.isFullyCollapsed()) {
+            if (mPanelExpansionInteractor.isFullyCollapsed()) {
                 mShadeViewController.updateTouchableRegion();
             }
         } else {
             boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
                     && mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-            if (!mShadeViewController.isFullyCollapsed()
+            if (!mPanelExpansionInteractor.isFullyCollapsed()
                     || mShadeViewController.isTracking()
                     || bypassKeyguard) {
                 // We are currently tracking or is open and the shade doesn't need to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7dd328a..14e934fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -76,6 +76,8 @@
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.keyguard.shared.model.DismissAction;
 import com.android.systemui.keyguard.shared.model.KeyguardDone;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -105,6 +107,8 @@
 
 import dagger.Lazy;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -116,6 +120,7 @@
 
 import kotlinx.coroutines.CoroutineDispatcher;
 import kotlinx.coroutines.ExperimentalCoroutinesApi;
+import kotlinx.coroutines.Job;
 
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
@@ -163,6 +168,9 @@
     private final Lazy<ShadeController> mShadeController;
     private final Lazy<SceneInteractor> mSceneInteractorLazy;
 
+    private Job mListenForAlternateBouncerTransitionSteps = null;
+    private Job mListenForKeyguardAuthenticatedBiometricsHandled = null;
+
     // Local cache of expansion events, to avoid duplicates
     private float mFraction = -1f;
     private boolean mTracking = false;
@@ -491,6 +499,26 @@
             mDockManager.addListener(mDockEventListener);
             mIsDocked = mDockManager.isDocked();
         }
+        if (mListenForAlternateBouncerTransitionSteps != null) {
+            mListenForAlternateBouncerTransitionSteps.cancel(null);
+        }
+        mListenForAlternateBouncerTransitionSteps = null;
+        if (mListenForKeyguardAuthenticatedBiometricsHandled != null) {
+            mListenForKeyguardAuthenticatedBiometricsHandled.cancel(null);
+        }
+        mListenForKeyguardAuthenticatedBiometricsHandled = null;
+        if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+            mListenForAlternateBouncerTransitionSteps = mJavaAdapter.alwaysCollectFlow(
+                    mKeyguardTransitionInteractor.transitionStepsFromState(
+                            KeyguardState.ALTERNATE_BOUNCER),
+                    this::consumeFromAlternateBouncerTransitionSteps
+            );
+
+            mListenForKeyguardAuthenticatedBiometricsHandled = mJavaAdapter.alwaysCollectFlow(
+                    mPrimaryBouncerInteractor.getKeyguardAuthenticatedBiometricsHandled(),
+                    this::consumeKeyguardAuthenticatedBiometricsHandled
+            );
+        }
 
         if (KeyguardWmStateRefactor.isEnabled()) {
             // Show the keyguard views whenever we've told WM that the lockscreen is visible.
@@ -514,6 +542,22 @@
         }
     }
 
+    @VisibleForTesting
+    void consumeFromAlternateBouncerTransitionSteps(TransitionStep step) {
+        hideAlternateBouncer(false);
+    }
+
+    /**
+     * Required without fix for b/328643370: missing AlternateBouncer (when occluded) => Gone
+     * transition.
+     */
+    @VisibleForTesting
+    void consumeKeyguardAuthenticatedBiometricsHandled(Unit handled) {
+        if (mAlternateBouncerInteractor.isVisibleState()) {
+            hideAlternateBouncer(false);
+        }
+    }
+
     private void consumeShowStatusBarKeyguardView(boolean show) {
         if (show != mLastShowing) {
             if (show) {
@@ -1458,7 +1502,6 @@
         mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedBiometrics(strongAuth);
 
         if (mAlternateBouncerInteractor.isVisibleState()) {
-            hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b5ab4e3..5d27467 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -61,7 +61,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -115,7 +115,7 @@
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
-    private final com.android.systemui.shade.ShadeController mShadeController;
+    private final ShadeController mShadeController;
     private final KeyguardStateController mKeyguardStateController;
     private final LockPatternUtils mLockPatternUtils;
     private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
@@ -126,7 +126,7 @@
     private final StatusBarNotificationActivityStarterLogger mLogger;
 
     private final NotificationPresenter mPresenter;
-    private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final ActivityTransitionAnimator mActivityTransitionAnimator;
     private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
@@ -163,7 +163,7 @@
             StatusBarNotificationActivityStarterLogger logger,
             OnUserInteractionCallback onUserInteractionCallback,
             NotificationPresenter presenter,
-            ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
             NotificationShadeWindowController notificationShadeWindowController,
             ActivityTransitionAnimator activityTransitionAnimator,
             ShadeAnimationInteractor shadeAnimationInteractor,
@@ -192,13 +192,13 @@
         mLockPatternUtils = lockPatternUtils;
         mStatusBarRemoteInputCallback = remoteInputCallback;
         mActivityIntentHelper = activityIntentHelper;
+        mPanelExpansionInteractor = panelExpansionInteractor;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mShadeAnimationInteractor = shadeAnimationInteractor;
         mMetricsLogger = metricsLogger;
         mLogger = logger;
         mOnUserInteractionCallback = onUserInteractionCallback;
         mPresenter = presenter;
-        mShadeViewController = shadeViewController;
         mActivityTransitionAnimator = activityTransitionAnimator;
         mNotificationAnimationProvider = notificationAnimationProvider;
         mPowerInteractor = powerInteractor;
@@ -296,7 +296,7 @@
         }
 
         // Always defer the keyguard dismiss when animating.
-        return animate || !mShadeViewController.isFullyCollapsed();
+        return animate || !mPanelExpansionInteractor.isFullyCollapsed();
     }
 
     private void handleNotificationClickAfterPanelCollapsed(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 5b14259..5a3ae73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -236,7 +236,7 @@
 
     @Override
     public boolean isPresenterFullyCollapsed() {
-        return mNotificationPanel.isFullyCollapsed();
+        return mPanelExpansionInteractor.isFullyCollapsed();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
new file mode 100644
index 0000000..6aaf5d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.policy
+
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import javax.inject.Inject
+
+/*
+ * Control when heads up notifications show during an avalanche where notifications arrive in fast
+ * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
+ */
+@SysUISingleton
+class AvalancheController @Inject constructor() {
+
+    private val tag = "AvalancheController"
+    private val debug = false
+
+    // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
+    @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
+
+    // List of runnables to run for the HUN showing right now
+    private var headsUpEntryShowingRunnableList: MutableList<Runnable> = ArrayList()
+
+    // HeadsUpEntry waiting to show
+    // Use sortable list instead of priority queue for debugging
+    private val nextList: MutableList<HeadsUpEntry> = ArrayList()
+
+    // Map of HeadsUpEntry waiting to show, and runnables to run when it shows.
+    // Use HashMap instead of SortedMap for faster lookup, and also because the ordering
+    // provided by HeadsUpEntry.compareTo is not consistent over time or with HeadsUpEntry.equals
+    @VisibleForTesting var nextMap: MutableMap<HeadsUpEntry, MutableList<Runnable>> = HashMap()
+
+    // Map of Runnable to label for debugging only
+    private val debugRunnableLabelMap: MutableMap<Runnable, String> = HashMap()
+
+    // HeadsUpEntry we did not show at all because they are not the top priority hun in their batch
+    // For debugging only
+    @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
+
+    /**
+     * Run or delay Runnable for given HeadsUpEntry
+     */
+    fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+        if (!NotificationThrottleHun.isEnabled) {
+            runnable.run()
+            return
+        }
+        val fn = "[$label] => AvalancheController.update ${getKey(entry)}"
+
+        if (debug) {
+            debugRunnableLabelMap[runnable] = label
+        }
+
+        if (isShowing(entry)) {
+            log {"$fn => [update showing]" }
+            runnable.run()
+        } else if (entry in nextMap) {
+            log { "$fn => [update next]" }
+            nextMap[entry]?.add(runnable)
+        } else if (headsUpEntryShowing == null) {
+            log { "$fn => [showNow]" }
+            showNow(entry, arrayListOf(runnable))
+        } else {
+            // Clean up invalid state when entry is in list but not map and vice versa
+            if (entry in nextMap) nextMap.remove(entry)
+            if (entry in nextList) nextList.remove(entry)
+
+            addToNext(entry, runnable)
+
+            // Shorten headsUpEntryShowing display time
+            val nextIndex = nextList.indexOf(entry)
+            val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
+            if (isOnlyNextEntry) {
+                // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
+                // and goes to the isShowing case above
+                headsUpEntryShowing!!.updateEntry(false, "avalanche duration update")
+            }
+        }
+        logState("after $fn")
+    }
+
+    @VisibleForTesting
+    fun addToNext(entry: HeadsUpEntry, runnable: Runnable) {
+        nextMap[entry] = arrayListOf(runnable)
+        nextList.add(entry)
+    }
+
+    /**
+     * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
+     * all Runnables associated with that entry.
+     */
+    fun delete(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+        if (!NotificationThrottleHun.isEnabled) {
+            runnable.run()
+            return
+        }
+        val fn = "[$label] => AvalancheController.delete " + getKey(entry)
+
+        if (entry in nextMap) {
+            log { "$fn => [remove from next]" }
+            if (entry in nextMap) nextMap.remove(entry)
+            if (entry in nextList) nextList.remove(entry)
+        } else if (entry in debugDropSet) {
+            log { "$fn => [remove from dropset]" }
+            debugDropSet.remove(entry)
+        } else if (isShowing(entry)) {
+            log { "$fn => [remove showing ${getKey(entry)}]" }
+            runnable.run()
+            showNext()
+        } else {
+            log { "$fn => [removing untracked ${getKey(entry)}]" }
+        }
+        logState("after $fn")
+    }
+
+    /**
+     * Returns true if given HeadsUpEntry is the last one tracked by AvalancheController. Used by
+     * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration during active
+     * avalanche.
+     */
+    fun shortenDuration(entry: HeadsUpEntry): Boolean {
+        if (!NotificationThrottleHun.isEnabled) {
+            // Use default display duration, like we always did before AvalancheController existed
+            return false
+        }
+        val showingList: MutableList<HeadsUpEntry> = mutableListOf()
+        headsUpEntryShowing?.let { showingList.add(it) }
+        val allEntryList = showingList + nextList
+
+        // Shorten duration if not last entry
+        return allEntryList.indexOf(entry) != allEntryList.size - 1
+    }
+
+    /**
+     * Return true if entry is waiting to show.
+     */
+    fun isWaiting(key: String): Boolean {
+        if (!NotificationThrottleHun.isEnabled) {
+            return false
+        }
+        for (entry in nextMap.keys) {
+            if (entry.mEntry?.key.equals(key)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Return list of keys for huns waiting
+     */
+    fun getWaitingKeys(): MutableList<String> {
+        if (!NotificationThrottleHun.isEnabled) {
+            return mutableListOf()
+        }
+        val keyList = mutableListOf<String>()
+        for (entry in nextMap.keys) {
+            entry.mEntry?.let { keyList.add(entry.mEntry!!.key) }
+        }
+        return keyList
+    }
+
+    private fun isShowing(entry: HeadsUpEntry): Boolean {
+        return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key
+    }
+
+    private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
+        log { "show " + getKey(entry) + " backlog size: " + runnableList.size }
+
+        headsUpEntryShowing = entry
+
+        runnableList.forEach {
+            if (it in debugRunnableLabelMap) {
+                log { "run runnable from: ${debugRunnableLabelMap[it]}" }
+            }
+            it.run()
+        }
+    }
+
+    private fun showNext() {
+        log { "showNext" }
+        headsUpEntryShowing = null
+
+        if (nextList.isEmpty()) {
+            log { "no more to show!" }
+            return
+        }
+
+        // Only show first (top priority) entry in next batch
+        nextList.sort()
+        headsUpEntryShowing = nextList[0]
+        headsUpEntryShowingRunnableList = nextMap[headsUpEntryShowing]!!
+
+        // Remove runnable labels for dropped huns
+        val listToDrop = nextList.subList(1, nextList.size)
+        if (debug) {
+            // Clear runnable labels
+            for (e in listToDrop) {
+                val runnableList = nextMap[e]!!
+                for (r in runnableList) {
+                    debugRunnableLabelMap.remove(r)
+                }
+            }
+            debugDropSet.addAll(listToDrop)
+        }
+
+        clearNext()
+        showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
+    }
+
+    fun clearNext() {
+        nextList.clear()
+        nextMap.clear()
+    }
+
+    // Methods below are for logging only ==========================================================
+
+    private inline fun log(s: () -> String) {
+        if (debug) {
+            Log.d(tag, s())
+        }
+    }
+
+    // TODO(b/315362456) expose as dumpable for bugreports
+    private fun logState(reason: String) {
+        log { "state $reason" }
+        log { "showing: " + getKey(headsUpEntryShowing) }
+        log { "next list: $nextListStr map: $nextMapStr" }
+        log { "drop: $dropSetStr" }
+    }
+
+    private val dropSetStr: String
+        get() {
+            val queue = ArrayList<String>()
+            for (entry in debugDropSet) {
+                queue.add(getKey(entry))
+            }
+            return java.lang.String.join(" ", queue)
+        }
+
+    private val nextListStr: String
+        get() {
+            val queue = ArrayList<String>()
+            for (entry in nextList) {
+                queue.add(getKey(entry))
+            }
+            return java.lang.String.join(" ", queue)
+        }
+
+    private val nextMapStr: String
+        get() {
+            val queue = ArrayList<String>()
+            for (entry in nextMap.keys) {
+                queue.add(getKey(entry))
+            }
+            return java.lang.String.join(" ", queue)
+        }
+
+    fun getKey(entry: HeadsUpEntry?): String {
+        if (entry == null) {
+            return "null"
+        }
+        if (entry.mEntry == null) {
+            return entry.toString()
+        }
+        return entry.mEntry!!.key
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 530e49c..05cc73e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -45,6 +45,8 @@
 import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.stream.Stream;
 
 /**
@@ -68,6 +70,7 @@
     private final AccessibilityManagerWrapper mAccessibilityMgr;
 
     private final UiEventLogger mUiEventLogger;
+    private final AvalancheController mAvalancheController;
 
     protected final SystemClock mSystemClock;
     protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
@@ -100,13 +103,15 @@
             SystemClock systemClock,
             @Main DelayableExecutor executor,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            AvalancheController avalancheController) {
         mLogger = logger;
         mExecutor = executor;
         mSystemClock = systemClock;
         mContext = context;
         mAccessibilityMgr = accessibilityManagerWrapper;
         mUiEventLogger = uiEventLogger;
+        mAvalancheController = avalancheController;
         Resources resources = context.getResources();
         mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
         mStickyForSomeTimeAutoDismissTime = resources.getInteger(
@@ -157,18 +162,26 @@
      */
     @Override
     public void showNotification(@NonNull NotificationEntry entry) {
-        mLogger.logShowNotification(entry);
-
-        // Add new entry and begin managing it
         HeadsUpEntry headsUpEntry = createHeadsUpEntry();
-        headsUpEntry.setEntry(entry);
-        mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
-        onEntryAdded(headsUpEntry);
-        entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        entry.setIsHeadsUpEntry(true);
 
-        updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
-        entry.setInterruption();
+        // Attach NotificationEntry for AvalancheController to log key and
+        // record mPostTime for AvalancheController sorting
+        headsUpEntry.setEntry(entry);
+
+        Runnable runnable = () -> {
+            // TODO(b/315362456) log outside runnable too
+            mLogger.logShowNotification(entry);
+
+            // Add new entry and begin managing it
+            mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+            onEntryAdded(headsUpEntry);
+            entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+            entry.setIsHeadsUpEntry(true);
+
+            updateNotificationInternal(entry.getKey(), true /* shouldHeadsUpAgain */);
+            entry.setInterruption();
+        };
+        mAvalancheController.update(headsUpEntry, runnable, "showNotification");
     }
 
     /**
@@ -181,6 +194,11 @@
     @Override
     public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
         mLogger.logRemoveNotification(key, releaseImmediately);
+
+        if (mAvalancheController.isWaiting(key)) {
+            removeEntry(key);
+            return true;
+        }
         HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
         if (headsUpEntry == null) {
             return true;
@@ -203,6 +221,14 @@
      */
     public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
         HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        Runnable runnable = () -> {
+            updateNotificationInternal(key, shouldHeadsUpAgain);
+        };
+        mAvalancheController.update(headsUpEntry, runnable, "updateNotification");
+    }
+
+    private void updateNotificationInternal(@NonNull String key, boolean shouldHeadsUpAgain) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
         mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
         if (headsUpEntry == null) {
             // the entry was released before this update (i.e by a listener) This can happen
@@ -231,12 +257,16 @@
         for (String key : keysToRemove) {
             removeEntry(key);
         }
+        for (String key : mAvalancheController.getWaitingKeys()) {
+            removeEntry(key);
+        }
     }
 
     /**
      * Returns the entry if it is managed by this manager.
      * @param key key of notification
      * @return the entry
+     * TODO(b/315362456) See if caller needs to check AvalancheController waiting entries
      */
     @Nullable
     public NotificationEntry getEntry(@NonNull String key) {
@@ -251,6 +281,7 @@
     @NonNull
     @Override
     public Stream<NotificationEntry> getAllEntries() {
+        // TODO(b/315362456) See if callers need to check AvalancheController
         return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
     }
 
@@ -267,7 +298,7 @@
      * @return true if the notification is managed by this manager
      */
     public boolean isHeadsUpEntry(@NonNull String key) {
-        return mHeadsUpEntryMap.containsKey(key);
+        return mHeadsUpEntryMap.containsKey(key) || mAvalancheController.isWaiting(key);
     }
 
     /**
@@ -331,7 +362,7 @@
      * Manager-specific logic that should occur when an entry is added.
      * @param headsUpEntry entry added
      */
-    protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+    void onEntryAdded(HeadsUpEntry headsUpEntry) {
         NotificationEntry entry = headsUpEntry.mEntry;
         entry.setHeadsUp(true);
 
@@ -349,20 +380,24 @@
      */
     protected final void removeEntry(@NonNull String key) {
         HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
-        if (headsUpEntry == null) {
-            return;
-        }
-        NotificationEntry entry = headsUpEntry.mEntry;
 
-        // If the notification is animating, we will remove it at the end of the animation.
-        if (entry != null && entry.isExpandAnimationRunning()) {
-            return;
-        }
-        entry.demoteStickyHun();
-        mHeadsUpEntryMap.remove(key);
-        onEntryRemoved(headsUpEntry);
-        entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        headsUpEntry.reset();
+        Runnable runnable = () -> {
+            if (headsUpEntry == null) {
+                return;
+            }
+            NotificationEntry entry = headsUpEntry.mEntry;
+
+            // If the notification is animating, we will remove it at the end of the animation.
+            if (entry != null && entry.isExpandAnimationRunning()) {
+                return;
+            }
+            entry.demoteStickyHun();
+            mHeadsUpEntryMap.remove(key);
+            onEntryRemoved(headsUpEntry);
+            entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+            headsUpEntry.reset();
+        };
+        mAvalancheController.delete(headsUpEntry, runnable, "removeEntry");
     }
 
     /**
@@ -380,7 +415,7 @@
         }
     }
 
-    protected void updatePinnedMode() {
+    private void updatePinnedMode() {
         boolean hasPinnedNotification = hasPinnedNotificationInternal();
         if (hasPinnedNotification == mHasPinnedNotification) {
             return;
@@ -416,7 +451,9 @@
      * Snoozes all current Heads Up Notifications.
      */
     public void snooze() {
-        for (String key : mHeadsUpEntryMap.keySet()) {
+        List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet());
+        keySet.addAll(mAvalancheController.getWaitingKeys());
+        for (String key : keySet) {
             HeadsUpEntry entry = getHeadsUpEntry(key);
             String packageName = entry.mEntry.getSbn().getPackageName();
             String snoozeKey = snoozeKey(packageName, mUser);
@@ -432,6 +469,7 @@
 
     @Nullable
     protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
+        // TODO(b/315362456) See if callers need to check AvalancheController
         return (HeadsUpEntry) mHeadsUpEntryMap.get(key);
     }
 
@@ -515,18 +553,22 @@
      */
     public void unpinAll(boolean userUnPinned) {
         for (String key : mHeadsUpEntryMap.keySet()) {
-            HeadsUpEntry entry = getHeadsUpEntry(key);
-            setEntryPinned(entry, false /* isPinned */);
-            // maybe it got un sticky
-            entry.updateEntry(false /* updatePostTime */, "unpinAll");
+            HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
 
-            // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
-            // on the screen.
-            if (userUnPinned && entry.mEntry != null) {
-                if (entry.mEntry.mustStayOnScreen()) {
-                    entry.mEntry.setHeadsUpIsVisible();
+            Runnable runnable = () -> {
+                setEntryPinned(headsUpEntry, false /* isPinned */);
+                // maybe it got un sticky
+                headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
+
+                // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
+                // on the screen.
+                if (userUnPinned && headsUpEntry.mEntry != null) {
+                    if (headsUpEntry.mEntry.mustStayOnScreen()) {
+                        headsUpEntry.mEntry.setHeadsUpIsVisible();
+                    }
                 }
-            }
+            };
+            mAvalancheController.delete(headsUpEntry, runnable, "unpinAll");
         }
     }
 
@@ -606,6 +648,7 @@
      */
     @Override
     public boolean isSticky(String key) {
+        // TODO(b/315362456) See if callers need to check AvalancheController
         HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
         if (headsUpEntry != null) {
             return headsUpEntry.isSticky();
@@ -633,9 +676,10 @@
 
     /**
      * This represents a notification and how long it is in a heads up mode. It also manages its
-     * lifecycle automatically when created.
+     * lifecycle automatically when created. This class is public because it is exposed by methods
+     * of AvalancheController that take it as param.
      */
-    protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+    public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
         public boolean mRemoteInputActive;
         public boolean mUserActionMayIndirectlyRemove;
 
@@ -672,27 +716,41 @@
         }
 
         /**
+         * An interface that returns the amount of time left this HUN should show.
+         */
+        interface FinishTimeUpdater {
+            long updateAndGetTimeRemaining();
+        }
+
+        /**
          * Updates an entry's removal time.
          * @param updatePostTime whether or not to refresh the post time
          */
         public void updateEntry(boolean updatePostTime, @Nullable String reason) {
-            mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
+            Runnable runnable = () -> {
+                mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
 
-            final long now = mSystemClock.elapsedRealtime();
-            mEarliestRemovalTime = now + mMinimumDisplayTime;
+                final long now = mSystemClock.elapsedRealtime();
+                mEarliestRemovalTime = now + mMinimumDisplayTime;
 
-            if (updatePostTime) {
-                mPostTime = Math.max(mPostTime, now);
-            }
+                if (updatePostTime) {
+                    mPostTime = Math.max(mPostTime, now);
+                }
+            };
+            mAvalancheController.update(this, runnable, "updateEntry (updatePostTime)");
 
             if (isSticky()) {
-                removeAutoRemovalCallbacks("updateEntry (sticky)");
+                cancelAutoRemovalCallbacks("updateEntry (sticky)");
                 return;
             }
 
-            final long finishTime = calculateFinishTime();
-            final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
-            scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
+            FinishTimeUpdater finishTimeCalculator = () -> {
+                final long finishTime = calculateFinishTime();
+                final long now = mSystemClock.elapsedRealtime();
+                final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+                return timeLeft;
+            };
+            scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
         }
 
         /**
@@ -758,12 +816,31 @@
             }
         }
 
+        @Override
+        public int hashCode() {
+            if (mEntry == null) return super.hashCode();
+            int result = mEntry.getKey().hashCode();
+            result = 31 * result;
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) return true;
+            if (o == null || !(o instanceof HeadsUpEntry)) return false;
+            HeadsUpEntry otherHeadsUpEntry = (HeadsUpEntry) o;
+            if (mEntry != null && otherHeadsUpEntry.mEntry != null) {
+                return mEntry.getKey().equals(otherHeadsUpEntry.mEntry.getKey());
+            }
+            return false;
+        }
+
         public void setExpanded(boolean expanded) {
             this.mExpanded = expanded;
         }
 
         public void reset() {
-            removeAutoRemovalCallbacks("reset()");
+            cancelAutoRemovalCallbacks("reset()");
             mEntry = null;
             mRemoveRunnable = null;
             mExpanded = false;
@@ -773,37 +850,48 @@
         /**
          * Clear any pending removal runnables.
          */
-        public void removeAutoRemovalCallbacks(@Nullable String reason) {
-            final boolean removed = removeAutoRemovalCallbackInternal();
+        public void cancelAutoRemovalCallbacks(@Nullable String reason) {
+            Runnable runnable = () -> {
+                final boolean removed = cancelAutoRemovalCallbackInternal();
 
-            if (removed) {
-                mLogger.logAutoRemoveCanceled(mEntry, reason);
-            }
+                if (removed) {
+                    mLogger.logAutoRemoveCanceled(mEntry, reason);
+                }
+            };
+            mAvalancheController.update(this, runnable,
+                    reason + " removeAutoRemovalCallbacks");
         }
 
-        public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
-            if (mRemoveRunnable == null) {
-                Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
-                return;
-            }
+        public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
+                @NonNull String reason) {
 
-            final boolean removed = removeAutoRemovalCallbackInternal();
+            Runnable runnable = () -> {
+                long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
 
-            if (removed) {
-                mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
-            } else {
-                mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
-            }
+                if (mRemoveRunnable == null) {
+                    Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+                    return;
+                }
 
-            mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
-                    delayMillis);
+                final boolean deletedExistingRemovalRunnable = cancelAutoRemovalCallbackInternal();
+                mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+                        delayMs);
+
+                if (deletedExistingRemovalRunnable) {
+                    mLogger.logAutoRemoveRescheduled(mEntry, delayMs, reason);
+                } else {
+                    mLogger.logAutoRemoveScheduled(mEntry, delayMs, reason);
+                }
+            };
+            mAvalancheController.update(this, runnable,
+                    reason + " scheduleAutoRemovalCallback");
         }
 
-        public boolean removeAutoRemovalCallbackInternal() {
+        public boolean cancelAutoRemovalCallbackInternal() {
             final boolean scheduled = (mCancelRemoveRunnable != null);
 
             if (scheduled) {
-                mCancelRemoveRunnable.run();
+                mCancelRemoveRunnable.run();  // Delete removal runnable from Executor queue
                 mCancelRemoveRunnable = null;
             }
 
@@ -815,8 +903,12 @@
          */
         public void removeAsSoonAsPossible() {
             if (mRemoveRunnable != null) {
-                final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
-                scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
+
+                FinishTimeUpdater finishTimeCalculator = () -> {
+                    final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+                    return timeLeft;
+                };
+                scheduleAutoRemovalCallback(finishTimeCalculator, "removeAsSoonAsPossible");
             }
         }
 
@@ -834,9 +926,15 @@
          * {@link SystemClock#elapsedRealtime()}
          */
         protected long calculateFinishTime() {
-            final long duration = getRecommendedHeadsUpTimeoutMs(
-                    isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
-
+            int requestedTimeOutMs;
+            if (isStickyForSomeTime()) {
+                requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
+            } else if (mAvalancheController.shortenDuration(this)) {
+                requestedTimeOutMs = 1000;
+            } else {
+                requestedTimeOutMs = mAutoDismissTime;
+            }
+            final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
             return mPostTime + duration;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
index 71bce5e..9d74c58 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
@@ -18,11 +18,12 @@
 
 import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
 
 @VolumePanelScope
 class SpatialAudioAvailabilityCriteria
@@ -31,5 +32,11 @@
     ComponentAvailabilityCriteria {
 
     override fun isAvailable(): Flow<Boolean> =
-        interactor.isAvailable.map { it is SpatialAudioAvailabilityModel.SpatialAudio }
+        combine(interactor.isAvailable, interactor.isEnabled) { isAvailable, isEnabled ->
+            if (isAvailable is SpatialAudioAvailabilityModel.SpatialAudio) {
+                isEnabled !is SpatialAudioEnabledModel.Unknown
+            } else {
+                false
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index 6032bfe..298ca67 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -18,8 +18,9 @@
 
 import android.media.AudioDeviceAttributes
 import android.media.AudioDeviceInfo
-import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.media.PhoneMediaDevice
 import com.android.settingslib.media.domain.interactor.SpatializerInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
@@ -54,12 +55,9 @@
     private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> =
         mediaOutputInteractor.currentConnectedDevice
             .map { mediaDevice ->
-                mediaDevice ?: return@map null
-                val btDevice: CachedBluetoothDevice =
-                    (mediaDevice as? BluetoothMediaDevice)?.cachedDevice ?: return@map null
-                btDevice.getAudioDeviceAttributes()
+                if (mediaDevice == null) builtinSpeaker else mediaDevice.getAudioDeviceAttributes()
             }
-            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), builtinSpeaker)
 
     /**
      * Returns spatial audio availability model. It can be:
@@ -115,7 +113,7 @@
             .stateIn(
                 coroutineScope,
                 SharingStarted.Eagerly,
-                SpatialAudioEnabledModel.Disabled,
+                SpatialAudioEnabledModel.Unknown,
             )
 
     /**
@@ -137,34 +135,50 @@
         changes.emit(Unit)
     }
 
-    private suspend fun CachedBluetoothDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
-        return listOf(
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_BLE_HEADSET,
-                    address
-                ),
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_BLE_SPEAKER,
-                    address
-                ),
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_BLE_BROADCAST,
-                    address
-                ),
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
-                    address
-                ),
-                AudioDeviceAttributes(
-                    AudioDeviceAttributes.ROLE_OUTPUT,
-                    AudioDeviceInfo.TYPE_HEARING_AID,
-                    address
-                )
+    private suspend fun MediaDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
+        when (this) {
+            is PhoneMediaDevice -> return builtinSpeaker
+            is BluetoothMediaDevice -> {
+                val device = cachedDevice ?: return null
+                return listOf(
+                        AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.TYPE_BLE_HEADSET,
+                            device.address,
+                        ),
+                        AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                            device.address,
+                        ),
+                        AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.TYPE_BLE_BROADCAST,
+                            device.address,
+                        ),
+                        AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                            device.address,
+                        ),
+                        AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.TYPE_HEARING_AID,
+                            device.address,
+                        )
+                    )
+                    .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
+            }
+            else -> return null
+        }
+    }
+
+    private companion object {
+        val builtinSpeaker =
+            AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                ""
             )
-            .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
index 9735e5c..4255a40 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
@@ -39,4 +39,7 @@
 
     /** Head tracking is enabled. This also means that [SpatialAudioEnabled]. */
     data object HeadTrackingEnabled : SpatialAudioEnabled
+
+    /** Spatial audio enabled state is unknown. */
+    data object Unknown : SpatialAudioEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
index 0c91bbf..ecd89ea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
@@ -24,75 +24,24 @@
 class VolumeSliderInteractor @Inject constructor() {
 
     /** mimic percentage volume setting */
-    val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
+    private val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
 
     /**
      * Translates [volume], that belongs to [volumeRange] to the value that belongs to
      * [displayValueRange].
-     *
-     * [currentValue] is the raw value received from the slider. Returns [currentValue] when it
-     * translates to the same volume as [volume] parameter. This ensures smooth slider experience
-     * (avoids snapping when the user stops dragging).
      */
     fun processVolumeToValue(
         volume: Int,
         volumeRange: ClosedRange<Int>,
-        currentValue: Float?,
-        isMuted: Boolean,
     ): Float {
-        if (isMuted) {
-            return 0f
-        }
-        val changedVolume: Int? = currentValue?.let { translateValueToVolume(it, volumeRange) }
-        return if (volume != volumeRange.start && volume == changedVolume) {
-            currentValue
-        } else {
-            translateToRange(
-                currentValue = volume.toFloat(),
-                currentRangeStart = volumeRange.start.toFloat(),
-                currentRangeEnd = volumeRange.endInclusive.toFloat(),
-                targetRangeStart = displayValueRange.start,
-                targetRangeEnd = displayValueRange.endInclusive,
-            )
-        }
-    }
-
-    /** Translates [value] from [displayValueRange] to volume that has [volumeRange]. */
-    fun translateValueToVolume(
-        value: Float,
-        volumeRange: ClosedRange<Int>,
-    ): Int {
-        return translateToRange(
-                currentValue = value,
-                currentRangeStart = displayValueRange.start,
-                currentRangeEnd = displayValueRange.endInclusive,
-                targetRangeStart = volumeRange.start.toFloat(),
-                targetRangeEnd = volumeRange.endInclusive.toFloat(),
-            )
-            .toInt()
-    }
-
-    /**
-     * Translates a value from one range to another.
-     *
-     * ```
-     * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
-     * Result: 37.5
-     * ```
-     */
-    private fun translateToRange(
-        currentValue: Float,
-        currentRangeStart: Float,
-        currentRangeEnd: Float,
-        targetRangeStart: Float,
-        targetRangeEnd: Float,
-    ): Float {
-        val currentRangeLength: Float = (currentRangeEnd - currentRangeStart)
-        val targetRangeLength: Float = targetRangeEnd - targetRangeStart
+        val currentRangeStart: Float = volumeRange.start.toFloat()
+        val targetRangeStart: Float = displayValueRange.start
+        val currentRangeLength: Float = (volumeRange.endInclusive.toFloat() - currentRangeStart)
+        val targetRangeLength: Float = displayValueRange.endInclusive - targetRangeStart
         if (currentRangeLength == 0f || targetRangeLength == 0f) {
             return 0f
         }
-        val volumeFraction: Float = (currentValue - currentRangeStart) / currentRangeLength
+        val volumeFraction: Float = (volume.toFloat() - currentRangeStart) / currentRangeLength
         return targetRangeStart + volumeFraction * targetRangeLength
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 532e517..1b73208 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -28,6 +28,7 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -71,50 +72,49 @@
             AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
         )
 
-    private var value = 0f
     override val slider: StateFlow<SliderState> =
         combine(
                 audioVolumeInteractor.getAudioStream(audioStream),
                 audioVolumeInteractor.canChangeVolume(audioStream),
                 audioVolumeInteractor.ringerMode,
             ) { model, isEnabled, ringerMode ->
-                model.toState(value, isEnabled, ringerMode)
+                model.toState(isEnabled, ringerMode)
             }
             .stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
 
-    override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+    override fun onValueChanged(state: SliderState, newValue: Float) {
         val audioViewModel = state as? State
         audioViewModel ?: return
         coroutineScope.launch {
-            value = newValue
-            val volume =
-                volumeSliderInteractor.translateValueToVolume(
-                    newValue,
-                    audioViewModel.audioStreamModel.volumeRange
-                )
-            audioVolumeInteractor.setVolume(audioStream, volume)
+            audioVolumeInteractor.setVolume(audioStream, newValue.roundToInt())
+        }
+    }
+
+    override fun toggleMuted(state: SliderState) {
+        val audioViewModel = state as? State
+        audioViewModel ?: return
+        coroutineScope.launch {
+            audioVolumeInteractor.setMuted(audioStream, !audioViewModel.audioStreamModel.isMuted)
         }
     }
 
     private fun AudioStreamModel.toState(
-        value: Float,
         isEnabled: Boolean,
         ringerMode: RingerMode,
     ): State {
         return State(
-            value =
-                volumeSliderInteractor.processVolumeToValue(
-                    volume,
-                    volumeRange,
-                    value,
-                    isMuted,
+            value = volume.toFloat(),
+            valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
+            valueText =
+                SliderViewModel.formatValue(
+                    volumeSliderInteractor.processVolumeToValue(volume, volumeRange)
                 ),
-            valueRange = volumeSliderInteractor.displayValueRange,
             icon = getIcon(ringerMode),
             label = labelsByStream[audioStream]?.let(context::getString)
                     ?: error("No label for the stream: $audioStream"),
             disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
             isEnabled = isEnabled,
+            a11yStep = volumeRange.step,
             audioStreamModel = this,
         )
     }
@@ -156,8 +156,10 @@
         override val valueRange: ClosedFloatingPointRange<Float>,
         override val icon: Icon,
         override val label: String,
+        override val valueText: String,
         override val disabledMessage: String?,
         override val isEnabled: Boolean,
+        override val a11yStep: Int,
         val audioStreamModel: AudioStreamModel,
     ) : SliderState
 
@@ -165,8 +167,10 @@
         override val value: Float = 0f
         override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
         override val icon: Icon? = null
+        override val valueText: String = ""
         override val label: String = ""
         override val disabledMessage: String? = null
+        override val a11yStep: Int = 0
         override val isEnabled: Boolean = true
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index ae93826..86b2d73 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -26,8 +26,8 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -46,46 +46,46 @@
 ) : SliderViewModel {
 
     private val volumeRange = 0..routingSession.routingSessionInfo.volumeMax
-    private val value = MutableStateFlow(0f)
 
     override val slider: StateFlow<SliderState> =
-        combine(value, mediaOutputInteractor.currentConnectedDevice) { value, _ ->
-                getCurrentState(value)
-            }
-            .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState(value.value))
+        combine(mediaOutputInteractor.currentConnectedDevice) { _ -> getCurrentState() }
+            .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState())
 
-    override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+    override fun onValueChanged(state: SliderState, newValue: Float) {
         coroutineScope.launch {
-            value.value = newValue
-            castVolumeInteractor.setVolume(
-                routingSession,
-                volumeSliderInteractor.translateValueToVolume(newValue, volumeRange),
-            )
+            castVolumeInteractor.setVolume(routingSession, newValue.roundToInt())
         }
     }
 
-    private fun getCurrentState(value: Float): State {
-        return State(
-            value =
-                volumeSliderInteractor.processVolumeToValue(
-                    volume = routingSession.routingSessionInfo.volume,
-                    volumeRange = volumeRange,
-                    currentValue = value,
-                    isMuted = false,
-                ),
-            valueRange = volumeSliderInteractor.displayValueRange,
+    override fun toggleMuted(state: SliderState) {
+        // do nothing because this action isn't supported for Cast sliders.
+    }
+
+    private fun getCurrentState(): State =
+        State(
+            value = routingSession.routingSessionInfo.volume.toFloat(),
+            valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
             icon = Icon.Resource(R.drawable.ic_cast, null),
+            valueText =
+                SliderViewModel.formatValue(
+                    volumeSliderInteractor.processVolumeToValue(
+                        volume = routingSession.routingSessionInfo.volume,
+                        volumeRange = volumeRange,
+                    )
+                ),
             label = context.getString(R.string.media_device_cast),
             isEnabled = true,
+            a11yStep = 1
         )
-    }
 
     private data class State(
         override val value: Float,
         override val valueRange: ClosedFloatingPointRange<Float>,
         override val icon: Icon,
+        override val valueText: String,
         override val label: String,
         override val isEnabled: Boolean,
+        override val a11yStep: Int,
     ) : SliderState {
         override val disabledMessage: String?
             get() = null
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index 6e9794b..b87d0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -27,7 +27,13 @@
     val value: Float
     val valueRange: ClosedFloatingPointRange<Float>
     val icon: Icon?
-    val label: String
-    val disabledMessage: String?
     val isEnabled: Boolean
+    val valueText: String
+    val label: String
+    /**
+     * A11y slider controls works by adjusting one step up or down. The default slider step isn't
+     * enough to trigger rounding to the correct value.
+     */
+    val a11yStep: Int
+    val disabledMessage: String?
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index 0c4b322..e78f833 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -23,5 +23,12 @@
 
     val slider: StateFlow<SliderState>
 
-    fun onValueChangeFinished(state: SliderState, newValue: Float)
+    fun onValueChanged(state: SliderState, newValue: Float)
+
+    fun toggleMuted(state: SliderState)
+
+    companion object {
+
+        fun formatValue(value: Float): String = "%.0f".format(value)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
index 7fd9c8a..635191a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -46,12 +46,18 @@
                     !footerComponents.contains(it.key) &&
                     it.key != bottomBar
             }
-        val headerComponents = components.filter { headerComponents.contains(it.key) }
-        val footerComponents = components.filter { footerComponents.contains(it.key) }
+        val headerComponents =
+            components
+                .filter { it.key in headerComponents }
+                .sortedBy { headerComponents.indexOf(it.key) }
+        val footerComponents =
+            components
+                .filter { it.key in footerComponents }
+                .sortedBy { footerComponents.indexOf(it.key) }
         return ComponentsLayout(
-            headerComponents = headerComponents.sortedBy { it.key },
+            headerComponents = headerComponents,
             contentComponents = contentComponents.sortedBy { it.key },
-            footerComponents = footerComponents.sortedBy { it.key },
+            footerComponents = footerComponents,
             bottomBarComponent = components.find { it.key == bottomBar }
                     ?: error(
                         "VolumePanelComponents.BOTTOM_BAR must be present in the default " +
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 7674fe9..e18bc04 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -260,6 +261,13 @@
                 splitScreen.goToFullscreenFromSplit();
             }
         });
+        splitScreen.registerSplitAnimationListener(new SplitScreen.SplitInvocationListener() {
+            @Override
+            public void onSplitAnimationInvoked(boolean animationRunning) {
+                mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION, animationRunning)
+                        .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+            }
+        }, mSysUiMainExecutor);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index e893eb1..11fe862 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -30,12 +30,14 @@
 import static org.mockito.Mockito.when;
 
 import android.animation.AnimatorTestRule;
+import android.platform.test.annotations.DisableFlags;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 
 import com.android.app.animation.Interpolators;
+import com.android.systemui.Flags;
 import com.android.systemui.animation.ViewHierarchyAnimator;
 import com.android.systemui.plugins.clocks.ClockConfig;
 import com.android.systemui.plugins.clocks.ClockController;
@@ -80,6 +82,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onLocaleListChangedNotifiesClockSwitchController() {
         ArgumentCaptor<ConfigurationListener> configurationListenerArgumentCaptor =
                 ArgumentCaptor.forClass(ConfigurationListener.class);
@@ -239,6 +242,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void statusAreaHeightChange_animatesHeightOutputChange() {
         // Init & Capture Layout Listener
         mController.onInit();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 976cd5bd..1f7d033 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -90,12 +90,12 @@
         mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
                 mSecureSettings));
         mMenuView.setTranslationY(halfScreenHeight);
-        doNothing().when(mMenuView).gotoEditScreen();
 
         mMenuViewLayer = spy(new MenuViewLayer(
                 mContext, stubWindowManager, mAccessibilityManager,
                 stubMenuViewModel, stubMenuViewAppearance, mMenuView,
                 mock(IAccessibilityFloatingMenu.class), mSecureSettings));
+        doNothing().when(mMenuViewLayer).gotoEditScreen();
 
         doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
         mStubListView = new RecyclerView(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index de795a7..de696f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.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;
@@ -49,6 +50,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.graphics.Insets;
@@ -136,6 +138,8 @@
     private WindowManager mStubWindowManager;
     @Mock
     private AccessibilityManager mStubAccessibilityManager;
+    @Mock
+    private PackageManager mMockPackageManager;
     private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
 
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
@@ -162,14 +166,15 @@
                 new MenuView(mSpyContext, mMenuViewModel, menuViewAppearance, mSecureSettings));
         // Ensure tests don't actually update metrics.
         doNothing().when(mMenuView).incrementTexMetric(any(), anyInt());
-        doNothing().when(mMenuView).gotoEditScreen();
 
         mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
                 mStubAccessibilityManager, mMenuViewModel, menuViewAppearance, mMenuView,
                 mFloatingMenu, mSecureSettings));
-        mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
         mMenuAnimationController = mMenuView.getMenuAnimationController();
 
+        doNothing().when(mSpyContext).startActivity(any());
+        when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
+
         mLastAccessibilityButtonTargets =
                 Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
                         Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
@@ -277,9 +282,31 @@
 
     @Test
     @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
-    public void onEditAction_gotoEditScreen_isCalled() {
+    public void onEditAction_startsActivity() {
+        mockActivityQuery(true);
         mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
-        verify(mMenuView).gotoEditScreen();
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mSpyContext).startActivity(intentCaptor.capture());
+        assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+                mMenuViewLayer.getIntentForEditScreen().getAction());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void onEditAction_noResolve_doesNotStart() {
+        mockActivityQuery(false);
+        mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
+        verify(mSpyContext, never()).startActivity(any());
+    }
+
+    @Test
+    public void getIntentForEditScreen_validate() {
+        Intent intent = mMenuViewLayer.getIntentForEditScreen();
+        String[] targets = intent.getBundleExtra(
+                ":settings:show_fragment_args").getStringArray("targets");
+
+        assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+        assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
     }
 
     @Test
@@ -308,20 +335,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
-    public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() {
-        mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
-        final PointF beforePosition = mMenuView.getMenuPosition();
-
-        dispatchShowingImeInsets();
-
-        final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
-        assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
-        assertThat(menuBottom).isLessThan(beforePosition.y);
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
     public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
         mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
         final PointF beforePosition = mMenuView.getMenuPosition();
@@ -337,19 +350,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
-    public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() {
-        mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
-        final PointF beforePosition = mMenuView.getMenuPosition();
-
-        dispatchHidingImeInsets();
-
-        assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
-        assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
     public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
         mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
         final PointF beforePosition = mMenuView.getMenuPosition();
@@ -527,4 +527,15 @@
         magnetListener.onReleasedInTarget(
                 new MagnetizedObject.MagneticTarget(view, 200), mock(MagnetizedObject.class));
     }
+
+    private void mockActivityQuery(boolean successfulQuery) {
+        // Query just needs to return a non-empty set to be successful.
+        ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
+        if (successfulQuery) {
+            resolveInfos.add(new ResolveInfo());
+        }
+        when(mMockPackageManager.queryIntentActivities(
+                any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index eced465..f6288b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -22,19 +22,13 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.UiModeManager;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
 import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
@@ -58,8 +52,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.ArrayList;
-
 /** Tests for {@link MenuView}. */
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -79,8 +71,6 @@
     private AccessibilityManager mAccessibilityManager;
 
     private SysuiTestableContext mSpyContext;
-    @Mock
-    private PackageManager mMockPackageManager;
 
     @Before
     public void setUp() throws Exception {
@@ -91,7 +81,6 @@
         mSpyContext = spy(mContext);
         doNothing().when(mSpyContext).startActivity(any());
 
-        when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
         final SecureSettings secureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 secureSettings);
@@ -178,32 +167,6 @@
         assertThat(radiiAnimator.isStarted()).isTrue();
     }
 
-    @Test
-    public void getIntentForEditScreen_validate() {
-        Intent intent = mMenuView.getIntentForEditScreen();
-        String[] targets = intent.getBundleExtra(
-                ":settings:show_fragment_args").getStringArray("targets");
-
-        assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
-        assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
-    public void gotoEditScreen_sendsIntent() {
-        mockActivityQuery(true);
-        mMenuView.gotoEditScreen();
-        verify(mSpyContext).startActivity(any());
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
-    public void gotoEditScreen_noResolve_doesNotStart() {
-        mockActivityQuery(false);
-        mMenuView.gotoEditScreen();
-        verify(mSpyContext, never()).startActivity(any());
-    }
-
     private InstantInsetLayerDrawable getMenuViewInsetLayer() {
         return (InstantInsetLayerDrawable) mMenuView.getBackground();
     }
@@ -226,14 +189,4 @@
         mUiModeManager.setNightMode(mNightMode);
         Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, mLastPosition);
     }
-
-    private void mockActivityQuery(boolean successfulQuery) {
-        // Query just needs to return a non-empty set to be successful.
-        ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
-        if (successfulQuery) {
-            resolveInfos.add(new ResolveInfo());
-        }
-        when(mMockPackageManager.queryIntentActivities(
-                any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index ad80a06..8700001 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -27,7 +27,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -37,6 +39,7 @@
 import java.math.BigDecimal
 import java.math.RoundingMode
 import java.util.UUID
+import kotlinx.coroutines.flow.dropWhile
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
@@ -224,6 +227,101 @@
         }
 
     @Test
+    fun startingSecondManualTransitionWillCancelPreviousManualTransition() =
+        TestScope().runTest {
+            // Drop initial steps from OFF which are sent in the constructor
+            val steps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.transitions
+                    .dropWhile { step -> step.from == OFF }
+                    .onEach { steps.add(it) }
+                    .launchIn(this)
+
+            val firstUuid =
+                underTest.startTransition(
+                    TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
+                )
+            runCurrent()
+
+            checkNotNull(firstUuid)
+            underTest.updateTransition(firstUuid, 0.5f, TransitionState.RUNNING)
+            runCurrent()
+
+            val secondUuid =
+                underTest.startTransition(
+                    TransitionInfo(OWNER_NAME, AOD, DREAMING, animator = null)
+                )
+            runCurrent()
+
+            checkNotNull(secondUuid)
+            underTest.updateTransition(secondUuid, 0.7f, TransitionState.RUNNING)
+            // Trying to transition the old uuid should be ignored.
+            underTest.updateTransition(firstUuid, 0.6f, TransitionState.RUNNING)
+            runCurrent()
+
+            assertThat(steps)
+                .containsExactly(
+                    TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME),
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME),
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.CANCELED, OWNER_NAME),
+                    TransitionStep(AOD, DREAMING, 0.5f, TransitionState.STARTED, OWNER_NAME),
+                    TransitionStep(AOD, DREAMING, 0.7f, TransitionState.RUNNING, OWNER_NAME),
+                )
+                .inOrder()
+
+            job.cancel()
+        }
+
+    @Test
+    fun startingSecondTransitionWillCancelPreviousManualTransition() =
+        TestScope().runTest {
+            // Drop initial steps from OFF which are sent in the constructor
+            val steps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.transitions
+                    .dropWhile { step -> step.from == OFF }
+                    .onEach { steps.add(it) }
+                    .launchIn(this)
+
+            val uuid =
+                underTest.startTransition(
+                    TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
+                )
+            runCurrent()
+
+            checkNotNull(uuid)
+            underTest.updateTransition(uuid, 0.5f, TransitionState.RUNNING)
+            runCurrent()
+
+            // Start new transition to dreaming, should cancel previous one.
+            runner.startTransition(
+                this,
+                TransitionInfo(
+                    OWNER_NAME,
+                    AOD,
+                    DREAMING,
+                    getAnimator(),
+                    TransitionModeOnCanceled.RESET,
+                ),
+            )
+            runCurrent()
+
+            // Trying to transition the old uuid should be ignored.
+            underTest.updateTransition(uuid, 0.6f, TransitionState.RUNNING)
+            runCurrent()
+
+            assertThat(steps.take(3))
+                .containsExactly(
+                    TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME),
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME),
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.CANCELED, OWNER_NAME),
+                )
+                .inOrder()
+
+            job.cancel()
+        }
+
+    @Test
     fun attemptTomanuallyUpdateTransitionWithInvalidUUIDthrowsException() {
         underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
         assertThat(wtfHandler.failed).isTrue()
@@ -336,6 +434,7 @@
 
     private class WtfHandler : TerribleFailureHandler {
         var failed = false
+
         override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) {
             failed = true
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index 87391cc..d410dac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
 import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,6 +37,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.Mockito.verify
 
 @ExperimentalCoroutinesApi
 @RunWith(JUnit4::class)
@@ -48,6 +50,20 @@
     private val underTest = kosmos.alternateBouncerViewModel
 
     @Test
+    fun showPrimaryBouncer() =
+        testScope.runTest {
+            underTest.showPrimaryBouncer()
+            verify(statusBarKeyguardViewManager).showPrimaryBouncer(any())
+        }
+
+    @Test
+    fun hideAlternateBouncer() =
+        testScope.runTest {
+            underTest.hideAlternateBouncer()
+            verify(statusBarKeyguardViewManager).hideAlternateBouncer(any())
+        }
+
+    @Test
     fun transitionToAlternateBouncer_scrimAlphaUpdate() =
         testScope.runTest {
             val scrimAlphas by collectValues(underTest.scrimAlpha)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index 3122edb..761c411 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
@@ -66,6 +67,7 @@
     @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var dialogLauncherAnimator: DialogTransitionAnimator
+    @Mock private lateinit var panelInteractor: PanelInteractor
     @Mock private lateinit var userContextProvider: UserContextProvider
     @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
     @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@@ -96,6 +98,7 @@
                 keyguardDismissUtil,
                 keyguardStateController,
                 dialogLauncherAnimator,
+                panelInteractor,
                 userContextProvider,
                 delegateFactory,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
index 8986d99..cd1452a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
@@ -68,7 +68,7 @@
     fun testGetValue_valueUnset() {
         testScope.runTest {
             userRepository.setSelectedUserInfo(SYSTEM_USER)
-            val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
 
             runCurrent()
 
@@ -81,7 +81,7 @@
     fun testGetValue_valueFalse() {
         testScope.runTest {
             userRepository.setSelectedUserInfo(SYSTEM_USER)
-            val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
 
             secureSettings.putIntForUser(SETTING_NAME, DISABLED, UserHandle.USER_SYSTEM)
             runCurrent()
@@ -94,7 +94,7 @@
     fun testGetValue_valueTrue() {
         testScope.runTest {
             userRepository.setSelectedUserInfo(SYSTEM_USER)
-            val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
 
             secureSettings.putIntForUser(SETTING_NAME, ENABLED, UserHandle.USER_SYSTEM)
             runCurrent()
@@ -104,15 +104,16 @@
     }
 
     @Test
-    fun testGetValue_valueTrue_secondaryUser_returnUnset() {
+    fun testGetValue_valueTrue_secondaryUser_returnTrue() {
         testScope.runTest {
             userRepository.setSelectedUserInfo(SECONDARY_USER)
-            val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
 
+            secureSettings.putIntForUser(SETTING_NAME, DISABLED, SYSTEM_USER_ID)
             secureSettings.putIntForUser(SETTING_NAME, ENABLED, SECONDARY_USER_ID)
             runCurrent()
 
-            assertThat(actualValue).isEqualTo(UNSET)
+            assertThat(actualValue).isEqualTo(ENABLED)
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 3defee9..722387c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -28,7 +28,7 @@
 import android.content.pm.ApplicationInfo;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
-
+import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
@@ -50,6 +50,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
 
+@FlakyTest(bugId = 327655994) // Also b/324682425
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PluginInstanceTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index f89b9cd..66a306e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -53,8 +53,6 @@
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.FakeBroadcastDispatcher
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.log.core.LogLevel
@@ -150,7 +148,10 @@
 
     @Before
     fun setUp() {
-        val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
+        val userId = ActivityManager.getCurrentUser()
+        val user = UserInfo(userId, "Current user", /* flags = */ 0)
+
+        deviceProvisionedController.currentUser = userId
         userTracker.set(listOf(user), /* currentUserIndex = */ 0)
 
         provider.start()
@@ -823,6 +824,13 @@
     }
 
     @Test
+    fun testShouldFsi_userSetupIncomplete() {
+        ensureUserSetupIncompleteFsiState()
+        assertShouldFsi(buildFsiEntry())
+        assertNoEventsLogged()
+    }
+
+    @Test
     fun testShouldNotFsi_noHunOrKeyguard() {
         ensureNoHunOrKeyguardFsiState()
         assertShouldNotFsi(buildFsiEntry())
@@ -888,7 +896,8 @@
         var statusBarState: Int? = null,
         var keyguardIsShowing: Boolean = false,
         var keyguardIsOccluded: Boolean = false,
-        var deviceProvisioned: Boolean = true
+        var deviceProvisioned: Boolean = true,
+        var currentUserSetup: Boolean = true
     )
 
     protected fun setState(state: State): Unit =
@@ -925,6 +934,7 @@
             keyguardStateController.isShowing = keyguardIsShowing
 
             deviceProvisionedController.deviceProvisioned = deviceProvisioned
+            deviceProvisionedController.isCurrentUserSetup = currentUserSetup
         }
 
     protected fun ensureState(block: State.() -> Unit) =
@@ -999,6 +1009,18 @@
         hunSettingEnabled = false
         keyguardIsShowing = false
         deviceProvisioned = false
+        currentUserSetup = true
+        run(block)
+    }
+
+    protected fun ensureUserSetupIncompleteFsiState(block: State.() -> Unit = {}) = ensureState {
+        isInteractive = true
+        isDreaming = false
+        statusBarState = SHADE
+        hunSettingEnabled = false
+        keyguardIsShowing = false
+        deviceProvisioned = true
+        currentUserSetup = false
         run(block)
     }
 
@@ -1009,6 +1031,7 @@
         hunSettingEnabled = false
         keyguardIsShowing = false
         deviceProvisioned = true
+        currentUserSetup = true
         run(block)
     }
 
@@ -1216,7 +1239,7 @@
 
                     neb.setImportance(importance)
                     val channel =
-                            NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
+                        NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
                     channel.isImportantConversation = isImportantConversation
                     neb.setChannel(channel)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index ea4ae17..dcbb93a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractorImpl;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -78,6 +79,7 @@
     @Mock private CommandQueue mCommandQueue;
     @Mock private QuickSettingsController mQuickSettingsController;
     @Mock private ShadeViewController mShadeViewController;
+    @Mock private PanelExpansionInteractorImpl mPanelExpansionInteractor;
     @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final MetricsLogger mMetricsLogger = new FakeMetricsLogger();
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -112,6 +114,7 @@
                 mShadeController,
                 mCommandQueue,
                 mShadeViewController,
+                mPanelExpansionInteractor,
                 mRemoteInputQuickSettingsDisabler,
                 mMetricsLogger,
                 mKeyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 443dd6a..1463680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.shade.ShadeControllerImpl
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -67,6 +68,7 @@
 class PhoneStatusBarViewControllerTest : SysuiTestCase() {
 
     @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor
     @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
     @Mock private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent
@@ -195,7 +197,7 @@
     @Test
     fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
-        `when`(shadeViewController.isFullyCollapsed).thenReturn(true)
+        `when`(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
 
         view.onTouchEvent(event)
@@ -291,21 +293,22 @@
 
     private fun createAndInitController(view: PhoneStatusBarView): PhoneStatusBarViewController {
         return PhoneStatusBarViewController.Factory(
-                Optional.of(sysuiUnfoldComponent),
-                Optional.of(progressProvider),
-                featureFlags,
-                FakeSceneContainerFlags(),
-                userChipViewModel,
-                centralSurfacesImpl,
-                statusBarWindowStateController,
-                shadeControllerImpl,
-                shadeViewController,
-                windowRootView,
-                shadeLogger,
-                viewUtil,
-                configurationController,
-                mStatusOverlayHoverListenerFactory
-            )
+            Optional.of(sysuiUnfoldComponent),
+            Optional.of(progressProvider),
+            featureFlags,
+            FakeSceneContainerFlags(),
+            userChipViewModel,
+            centralSurfacesImpl,
+            statusBarWindowStateController,
+            shadeControllerImpl,
+            shadeViewController,
+            panelExpansionInteractor,
+            windowRootView,
+            shadeLogger,
+            viewUtil,
+            configurationController,
+            mStatusOverlayHoverListenerFactory
+        )
             .create(view)
             .also { it.init() }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 562aa6a..b0b9bec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -82,6 +82,9 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
@@ -103,6 +106,8 @@
 
 import com.google.common.truth.Truth;
 
+import kotlin.Unit;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -1045,4 +1050,35 @@
         verify(mCentralSurfaces, never()).hideKeyguard();
         verify(mPrimaryBouncerInteractor, never()).show(true);
     }
+
+    @Test
+    public void altBouncerNotVisible_keyguardAuthenticatedBiometricsHandled() {
+        clearInvocations(mAlternateBouncerInteractor);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+        mStatusBarKeyguardViewManager.consumeKeyguardAuthenticatedBiometricsHandled(Unit.INSTANCE);
+        verify(mAlternateBouncerInteractor, never()).hide();
+    }
+
+    @Test
+    public void altBouncerVisible_keyguardAuthenticatedBiometricsHandled() {
+        clearInvocations(mAlternateBouncerInteractor);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+        mStatusBarKeyguardViewManager.consumeKeyguardAuthenticatedBiometricsHandled(Unit.INSTANCE);
+        verify(mAlternateBouncerInteractor).hide();
+    }
+
+    @Test
+    public void fromAlternateBouncerTransitionStep() {
+        clearInvocations(mAlternateBouncerInteractor);
+        mStatusBarKeyguardViewManager.consumeFromAlternateBouncerTransitionSteps(
+                new TransitionStep(
+                        /* from */ KeyguardState.ALTERNATE_BOUNCER,
+                        /* to */ KeyguardState.GONE,
+                        /* value */ 1f,
+                        TransitionState.FINISHED,
+                        "StatusBarKeyguardViewManagerTest"
+                )
+        );
+        verify(mAlternateBouncerInteractor).hide();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 938b2f8..127a3d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -75,9 +75,9 @@
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeControllerImpl;
-import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -257,7 +257,7 @@
                         new StatusBarNotificationActivityStarterLogger(logcatLogBuffer()),
                         mOnUserInteractionCallback,
                         mock(NotificationPresenter.class),
-                        mock(ShadeViewController.class),
+                        mock(PanelExpansionInteractor.class),
                         mock(NotificationShadeWindowController.class),
                         mActivityTransitionAnimator,
                         new ShadeAnimationInteractorLegacyImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 3bf54a3..57a89b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -100,10 +100,6 @@
                 handler = handler
         )
         controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
-
-        // Screen off does not run if the panel is expanded, so we should say it's collapsed to test
-        // screen off.
-        `when`(shadeViewController.isFullyCollapsed).thenReturn(true)
     }
 
     @After
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 6cc1e8e..185deda 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -20,11 +20,13 @@
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 
 val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
     Kosmos.Fixture {
         KeyguardTransitionInteractor(
             scope = applicationCoroutineScope,
+            mainDispatcher = testDispatcher,
             repository = keyguardTransitionRepository,
             keyguardRepository = keyguardRepository,
             fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 7a86c4f..c6684af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -30,9 +30,9 @@
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.settings.userTracker
+import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
 import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
 import com.android.systemui.shade.shadeController
-import com.android.systemui.shade.shadeViewController
 import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
@@ -78,7 +78,7 @@
             statusBarNotificationActivityStarterLogger,
             onUserInteractionCallback,
             notificationPresenter,
-            shadeViewController,
+            panelExpansionInteractor,
             notificationShadeWindowController,
             activityTransitionAnimator,
             shadeAnimationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
index aa2c2a2..ebcabf8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
@@ -39,8 +39,16 @@
         callbacks.toSet().forEach { it.onUserSwitched() }
     }
 
-    fun setUserSetup(userId: Int) {
-        usersSetup.add(userId)
+    fun setUserSetup(userId: Int, isSetup: Boolean = true) {
+        if (isSetup) {
+            usersSetup.add(userId)
+        } else {
+            usersSetup.remove(userId)
+        }
         callbacks.toSet().forEach { it.onUserSetupChanged() }
     }
+
+    fun setCurrentUserSetup(isSetup: Boolean) {
+        setUserSetup(currentUser, isSetup)
+    }
 }
diff --git a/ravenwood/api-maintainers.md b/ravenwood/api-maintainers.md
index 4b2f968..c059cab 100644
--- a/ravenwood/api-maintainers.md
+++ b/ravenwood/api-maintainers.md
@@ -4,7 +4,7 @@
 
 To opt-in to supporting an API under Ravenwood, you can use the inline annotations documented below to customize your API behavior when running under Ravenwood.  Because these annotations are inline in the relevant platform source code, they serve as valuable reminders to future API maintainers of Ravenwood support expectations.
 
-> **Note:** to ensure that API teams are well-supported during early Ravenwood onboarding, the Ravenwood team is manually maintaining an allow-list of classes that are able to use Ravenwood annotations.  Please reach out to ravenwood@ so we can offer design advice and allow-list your APIs.
+> **Note:** Active development of Ravenwood has been paused as of March 2024. Currently supported APIs will continue working, but the addition of new APIs is not currently being supported. There is an allowlist that restricts where Ravenwood-specific annotations can be used, and that allowlist is not being expanded while development is paused.
 
 These Ravenwood-specific annotations have no bearing on the status of an API being public, `@SystemApi`, `@TestApi`, `@hide`, etc.  Ravenwood annotations are an orthogonal concept that are only consumed by the internal `hoststubgen` tool during a post-processing step that generates the Ravenwood runtime environment.  Teams that own APIs can continue to refactor opted-in `@hide` implementation details, as long as the test-visible behavior continues passing.
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4be303a..2d531e7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1706,20 +1706,101 @@
     }
 
     @Override
-    @RequiresPermission(Manifest.permission.STATUS_BAR_SERVICE)
+    @RequiresPermission(allOf = {
+            Manifest.permission.STATUS_BAR_SERVICE,
+            Manifest.permission.MANAGE_ACCESSIBILITY
+    })
     public void notifyQuickSettingsTilesChanged(
-            @UserIdInt int userId, List<ComponentName> tileComponentNames) {
-        mSecurityPolicy.enforceCallingPermission(
+            @UserIdInt int userId, @NonNull List<ComponentName> tileComponentNames) {
+        if (!android.view.accessibility.Flags.a11yQsShortcut()) {
+            return;
+        }
+
+        mContext.enforceCallingPermission(
                 Manifest.permission.STATUS_BAR_SERVICE,
                 /* function= */ "notifyQuickSettingsTilesChanged");
+        mContext.enforceCallingPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY,
+                /* function= */ "notifyQuickSettingsTilesChanged");
 
-        Slog.d(LOG_TAG, TextUtils.formatSimple(
-                "notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
-                        userId, tileComponentNames));
-        // TODO (b/314843909): in the follow up cl
+        if (DEBUG) {
+            Slog.d(LOG_TAG, TextUtils.formatSimple(
+                    "notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
+                    userId, tileComponentNames));
+        }
+        final Set<ComponentName> newTileComponentNames = new ArraySet<>(tileComponentNames);
+        final Set<ComponentName> addedTiles;
+        final Set<ComponentName> removedTiles;
+        final Map<ComponentName, AccessibilityServiceInfo> tileServiceToA11yServiceInfo;
+        final Map<ComponentName, ComponentName> a11yFeatureToTileService;
+
         // update in-memory copy of QS_TILES in AccessibilityManager
-        // update Settings.Secure.ACCESSIBILITY_QS_TARGETS and its in-memory copy
-        // show full device control warning if needed (b/314850435)
+        synchronized (mLock) {
+            AccessibilityUserState userState = getUserStateLocked(userId);
+
+            tileServiceToA11yServiceInfo = userState.getTileServiceToA11yServiceInfoMapLocked();
+            a11yFeatureToTileService = userState.getA11yFeatureToTileService();
+
+            ArraySet<ComponentName> currentTiles = userState.getA11yQsTilesInQsPanel();
+            // Find newly added tiles
+            addedTiles = newTileComponentNames
+                    .stream()
+                    .filter(tileComponentName -> !currentTiles.contains(tileComponentName))
+                    .collect(Collectors.toSet());
+            // Find newly removed tiles
+            removedTiles = currentTiles
+                    .stream()
+                    .filter(tileComponentName -> !newTileComponentNames.contains(tileComponentName))
+                    .collect(Collectors.toSet());
+
+            if (addedTiles.isEmpty() && removedTiles.isEmpty()) {
+                return;
+            }
+
+            userState.updateA11yTilesInQsPanelLocked(newTileComponentNames);
+        }
+
+        List<String> a11yFeaturesToEnable = new ArrayList<>();
+        List<String> a11yFeaturesToRemove = new ArrayList<>();
+        // Find the framework features to configure the qs shortcut on/off
+        for (Map.Entry<ComponentName, ComponentName> frameworkFeatureWithTile :
+                ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.entrySet()) {
+            String a11yFeature = frameworkFeatureWithTile.getKey().flattenToString();
+            ComponentName tile = frameworkFeatureWithTile.getValue();
+            if (addedTiles.contains(tile)) {
+                a11yFeaturesToEnable.add(a11yFeature);
+            } else if (removedTiles.contains(tile)) {
+                a11yFeaturesToRemove.add(a11yFeature);
+            }
+        }
+        // Find the accessibility service/activity to configure the qs shortcut on/off
+        for (Map.Entry<ComponentName, ComponentName> a11yFeatureWithTileService :
+                a11yFeatureToTileService.entrySet()) {
+            String a11yFeature = a11yFeatureWithTileService.getKey().flattenToString();
+            ComponentName tileService = a11yFeatureWithTileService.getValue();
+            if (addedTiles.contains(tileService)) {
+                AccessibilityServiceInfo serviceInfo = tileServiceToA11yServiceInfo.getOrDefault(
+                        tileService, null);
+                if (serviceInfo != null && isAccessibilityServiceWarningRequired(serviceInfo)) {
+                    // TODO(b/314850435): show full device control warning if needed after
+                    // SysUI QS Panel can update live
+                    continue;
+                }
+                a11yFeaturesToEnable.add(a11yFeature);
+            } else if (removedTiles.contains(tileService)) {
+                a11yFeaturesToRemove.add(a11yFeature);
+            }
+        }
+        // Turn on/off a11y qs shortcut for the a11y features based on the change in QS Panel
+        if (!a11yFeaturesToEnable.isEmpty()) {
+            enableShortcutForTargets(/* enable= */ true, UserShortcutType.QUICK_SETTINGS,
+                    a11yFeaturesToEnable, userId);
+        }
+
+        if (!a11yFeaturesToRemove.isEmpty()) {
+            enableShortcutForTargets(/* enable= */ false, UserShortcutType.QUICK_SETTINGS,
+                    a11yFeaturesToRemove, userId);
+        }
     }
 
     /**
@@ -3661,18 +3742,35 @@
     /**
      * Update the Settings.Secure.ACCESSIBILITY_QS_TARGETS so that it only contains valid content,
      * and a side loaded service can't spoof the package name of the default service.
+     * <p>
+     * 1. Remove the target if the target is no longer installed on the device <br/>
+     * 2. Add the target if the target is enabled and the target's tile is in the QS Panel <br/>
+     * </p>
      */
     private void updateAccessibilityQsTargetsLocked(AccessibilityUserState userState) {
-        final Set<String> targets =
-                userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
-        final int lastSize = targets.size();
-        if (lastSize == 0) {
+        if (!android.view.accessibility.Flags.a11yQsShortcut()) {
             return;
         }
 
+        final Set<String> targets =
+                userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
+
         // Removes the targets that are no longer installed on the device.
         boolean somethingChanged = targets.removeIf(
                 name -> !userState.isShortcutTargetInstalledLocked(name));
+        // Add the target if the a11y service is enabled and the tile exist in QS panel
+        Set<ComponentName> enabledServices = userState.getEnabledServicesLocked();
+        Map<ComponentName, ComponentName> a11yFeatureToTileService =
+                userState.getA11yFeatureToTileService();
+        Set<ComponentName> currentA11yTilesInQsPanel = userState.getA11yQsTilesInQsPanel();
+        for (ComponentName enabledService : enabledServices) {
+            ComponentName tileService =
+                    a11yFeatureToTileService.getOrDefault(enabledService, null);
+            if (tileService != null && currentA11yTilesInQsPanel.contains(tileService)) {
+                somethingChanged |= targets.add(enabledService.flattenToString());
+            }
+        }
+
         if (!somethingChanged) {
             return;
         }
@@ -3700,14 +3798,18 @@
             return;
         }
 
-        final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = List.of(
+        final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = new ArrayList<>(3);
+        shortcutTypeAndShortcutSetting.add(
                 new Pair<>(ACCESSIBILITY_SHORTCUT_KEY,
-                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
+                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
+        shortcutTypeAndShortcutSetting.add(
                 new Pair<>(ACCESSIBILITY_BUTTON,
-                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
-                new Pair<>(UserShortcutType.QUICK_SETTINGS,
-                        Settings.Secure.ACCESSIBILITY_QS_TARGETS)
-        );
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS));
+        if (android.view.accessibility.Flags.a11yQsShortcut()) {
+            shortcutTypeAndShortcutSetting.add(
+                    new Pair<>(UserShortcutType.QUICK_SETTINGS,
+                            Settings.Secure.ACCESSIBILITY_QS_TARGETS));
+        }
 
         final ComponentName serviceName = service.getComponentName();
         for (Pair<Integer, String> shortcutTypePair : shortcutTypeAndShortcutSetting) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 063eafe..4b128f7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -66,6 +66,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * Class that hold states and settings per user and share between
@@ -104,6 +106,15 @@
     final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
     private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();
 
+    /**
+     * The QuickSettings tiles in the QS Panel. This can be different from
+     * {@link #mAccessibilityQsTargets} in that {@link #mA11yTilesInQsPanel} stores the
+     * TileService's or the a11y framework tile component names (e.g.
+     * {@link AccessibilityShortcutController#COLOR_INVERSION_TILE_COMPONENT_NAME}) instead of the
+     * A11y Feature's component names.
+     */
+    private final ArraySet<ComponentName> mA11yTilesInQsPanel = new ArraySet<>();
+
     private final ServiceInfoChangeListener mServiceInfoChangeListener;
 
     private ComponentName mServiceChangingSoftKeyboardMode;
@@ -566,7 +577,9 @@
         pw.println("}");
         pw.append("     button target:{").append(mTargetAssignedToAccessibilityButton);
         pw.println("}");
-        pw.append("     qs shortcut targets:" + mAccessibilityQsTargets);
+        pw.append("     qs shortcut targets:").append(mAccessibilityQsTargets.toString());
+        pw.println();
+        pw.append("     a11y tiles in QS panel:").append(mA11yTilesInQsPanel.toString());
         pw.println();
         pw.append("     Bound services:{");
         final int serviceCount = mBoundServices.size();
@@ -1100,10 +1113,46 @@
         return new ArraySet<>(mAccessibilityQsTargets);
     }
 
+    public void updateA11yTilesInQsPanelLocked(Set<ComponentName> componentNames) {
+        mA11yTilesInQsPanel.clear();
+        mA11yTilesInQsPanel.addAll(componentNames);
+    }
+
+    /**
+     * Returns a copy of the a11y tiles that are in the QuickSettings panel
+     */
+    public ArraySet<ComponentName> getA11yQsTilesInQsPanel() {
+        return new ArraySet<>(mA11yTilesInQsPanel);
+    }
+
+    /**
+     * Returns a map of AccessibilityService or AccessibilityShortcut to its provided TileService
+     */
     public Map<ComponentName, ComponentName> getA11yFeatureToTileService() {
         Map<ComponentName, ComponentName> featureToTileServiceMap = new ArrayMap<>();
         featureToTileServiceMap.putAll(mA11yServiceToTileService);
         featureToTileServiceMap.putAll(mA11yActivityToTileService);
         return featureToTileServiceMap;
     }
+
+    /**
+     * Returns a map of TileService's componentName to the AccessibilityServiceInfo it ties to.
+     */
+    public Map<ComponentName, AccessibilityServiceInfo> getTileServiceToA11yServiceInfoMapLocked() {
+        Map<ComponentName, AccessibilityServiceInfo> tileServiceToA11yServiceInfoMap =
+                new ArrayMap<>();
+        Map<ComponentName, AccessibilityServiceInfo> a11yServiceToServiceInfoMap =
+                mInstalledServices.stream().collect(
+                        Collectors.toMap(
+                                AccessibilityServiceInfo::getComponentName,
+                                Function.identity()));
+        for (Map.Entry<ComponentName, ComponentName> serviceToTile :
+                mA11yServiceToTileService.entrySet()) {
+            if (a11yServiceToServiceInfoMap.containsKey(serviceToTile.getKey())) {
+                tileServiceToA11yServiceInfoMap.put(serviceToTile.getValue(),
+                        a11yServiceToServiceInfoMap.get(serviceToTile.getKey()));
+            }
+        }
+        return tileServiceToA11yServiceInfoMap;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 5298846..0012b3d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -204,6 +204,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
 import android.service.voice.HotwordDetectionService;
 import android.service.voice.VisualQueryDetectionService;
 import android.service.wearable.WearableSensingService;
@@ -4518,13 +4519,14 @@
     }
 
     // TODO(b/265746493): Special case for HotwordDetectionService,
-    // VisualQueryDetectionService and WearableSensingService.
+    // VisualQueryDetectionService, WearableSensingService and OnDeviceSandboxedInferenceService
     // Need a cleaner way to append this seInfo.
     private String generateAdditionalSeInfoFromService(Intent service) {
         if (service != null && service.getAction() != null
                 && (service.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE)
                 || service.getAction().equals(VisualQueryDetectionService.SERVICE_INTERFACE)
-                || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE))) {
+                || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE)
+            || service.getAction().equals(OnDeviceSandboxedInferenceService.SERVICE_INTERFACE))) {
             return ":isolatedComputeApp";
         }
         return "";
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 3d695bc..5cb8b95 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -674,14 +674,16 @@
         return mScheduleServiceTimeoutPending;
     }
 
-    @GuardedBy("mService")
     void onProcessUnfrozen() {
-        scheduleServiceTimeoutIfNeededLocked();
+        synchronized (mService) {
+            scheduleServiceTimeoutIfNeededLocked();
+        }
     }
 
-    @GuardedBy("mService")
     void onProcessFrozenCancelled() {
-        scheduleServiceTimeoutIfNeededLocked();
+        synchronized (mService) {
+            scheduleServiceTimeoutIfNeededLocked();
+        }
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d4f04b5..e8c05c6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7124,7 +7124,8 @@
 
         switch (mPlatformType) {
         case AudioSystem.PLATFORM_VOICE:
-            if (isInCommunication()) {
+            if (isInCommunication()
+                    || mAudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0)) {
                 if (mDeviceBroker.isBluetoothScoActive()) {
                     // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
                     return AudioSystem.STREAM_BLUETOOTH_SCO;
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 40b2f5a..10030b3 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -21,6 +21,7 @@
 import android.os.IBinder;
 import android.os.PowerManager;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.brightness.clamper.HdrClamper;
 import com.android.server.display.feature.DisplayManagerFlags;
 
@@ -30,8 +31,7 @@
 class BrightnessRangeController {
 
     private final HighBrightnessModeController mHbmController;
-    private final NormalBrightnessModeController mNormalBrightnessModeController =
-            new NormalBrightnessModeController();
+    private final NormalBrightnessModeController mNormalBrightnessModeController;
 
     private final HdrClamper mHdrClamper;
 
@@ -45,17 +45,21 @@
             Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
             DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
         this(hbmController, modeChangeCallback, displayDeviceConfig,
+                new NormalBrightnessModeController(),
                 new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags,
                 displayToken, info);
     }
 
+    @VisibleForTesting
     BrightnessRangeController(HighBrightnessModeController hbmController,
             Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
+            NormalBrightnessModeController normalBrightnessModeController,
             HdrClamper hdrClamper, DisplayManagerFlags flags, IBinder displayToken,
             DisplayDeviceInfo info) {
         mHbmController = hbmController;
         mModeChangeCallback = modeChangeCallback;
         mHdrClamper = hdrClamper;
+        mNormalBrightnessModeController = normalBrightnessModeController;
         mUseHdrClamper = flags.isHdrClamperEnabled();
         mUseNbmController = flags.isNbmControllerEnabled();
         if (mUseNbmController) {
@@ -126,8 +130,11 @@
 
 
     float getCurrentBrightnessMax() {
-        if (mUseNbmController && mHbmController.getHighBrightnessMode()
-                == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
+        // nbmController might adjust maxBrightness only if device does not support HBM or
+        // hbm is currently not allowed
+        if (mUseNbmController
+                && (!mHbmController.deviceSupportsHbm()
+                || !mHbmController.isHbmCurrentlyAllowed())) {
             return Math.min(mHbmController.getCurrentBrightnessMax(),
                     mNormalBrightnessModeController.getCurrentBrightnessMax());
         }
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index ab7c503..a12d248 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -42,6 +42,9 @@
 import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.display.utils.DebugUtils;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Listens for Skin thermal sensor events, disables external displays if thermal status becomes
  * equal or above {@link android.os.Temperature#THROTTLING_CRITICAL}, enables external displays if
@@ -106,6 +109,10 @@
     private final ExternalDisplayStatsService mExternalDisplayStatsService;
     @ThrottlingStatus
     private volatile int mStatus = THROTTLING_NONE;
+    //@GuardedBy("mSyncRoot")
+    private boolean mIsBootCompleted;
+    //@GuardedBy("mSyncRoot")
+    private final Set<Integer> mDisplayIdsWaitingForBootCompletion = new HashSet<>();
 
     ExternalDisplayPolicy(@NonNull final Injector injector) {
         mInjector = injector;
@@ -121,6 +128,17 @@
      * Starts listening for temperature changes.
      */
     void onBootCompleted() {
+        synchronized (mSyncRoot) {
+            mIsBootCompleted = true;
+            for (var displayId : mDisplayIdsWaitingForBootCompletion) {
+                var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+                if (logicalDisplay != null) {
+                    handleExternalDisplayConnectedLocked(logicalDisplay);
+                }
+            }
+            mDisplayIdsWaitingForBootCompletion.clear();
+        }
+
         if (!mFlags.isConnectedDisplayManagementEnabled()) {
             if (DEBUG) {
                 Slog.d(TAG, "External display management is not enabled on your device:"
@@ -189,6 +207,11 @@
             return;
         }
 
+        if (!mIsBootCompleted) {
+            mDisplayIdsWaitingForBootCompletion.add(logicalDisplay.getDisplayIdLocked());
+            return;
+        }
+
         mExternalDisplayStatsService.onDisplayConnected(logicalDisplay);
 
         if ((Build.IS_ENG || Build.IS_USERDEBUG)
@@ -227,7 +250,12 @@
             return;
         }
 
-        mExternalDisplayStatsService.onDisplayDisconnected(logicalDisplay.getDisplayIdLocked());
+        var displayId = logicalDisplay.getDisplayIdLocked();
+        if (mDisplayIdsWaitingForBootCompletion.remove(displayId)) {
+            return;
+        }
+
+        mExternalDisplayStatsService.onDisplayDisconnected(displayId);
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index a9f78fd..47176fe 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -168,7 +168,7 @@
     }
 
     float getCurrentBrightnessMax() {
-        if (!deviceSupportsHbm() || isCurrentlyAllowed()) {
+        if (!deviceSupportsHbm() || isHbmCurrentlyAllowed()) {
             // Either the device doesn't support HBM, or HBM range is currently allowed (device
             // it in a high-lux environment). In either case, return the highest brightness
             // level supported by the device.
@@ -356,7 +356,7 @@
         return event.getStartTimeMillis();
     }
 
-    private boolean isCurrentlyAllowed() {
+    boolean isHbmCurrentlyAllowed() {
         // Returns true if HBM is allowed (above the ambient lux threshold) and there's still
         // time within the current window for additional HBM usage. We return false if there is an
         // HDR layer because we don't want the brightness MAX to change for HDR, which has its
@@ -369,7 +369,7 @@
                 && !mIsBlockedByLowPowerMode);
     }
 
-    private boolean deviceSupportsHbm() {
+    boolean deviceSupportsHbm() {
         return mHbmData != null && mHighBrightnessModeMetadata != null;
     }
 
@@ -462,7 +462,7 @@
                     + ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
                     + ", remainingAllowedTime: " + remainingTime
                     + ", isLuxHigh: " + mIsInAllowedAmbientRange
-                    + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed()
+                    + ", isHBMCurrentlyAllowed: " + isHbmCurrentlyAllowed()
                     + ", isHdrLayerPresent: " + mIsHdrLayerPresent
                     + ", mMaxDesiredHdrSdrRatio: " + mMaxDesiredHdrSdrRatio
                     + ", isAutoBrightnessEnabled: " +  mIsAutoBrightnessEnabled
@@ -575,7 +575,7 @@
             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
         } else if (mIsHdrLayerPresent) {
             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
-        } else if (isCurrentlyAllowed()) {
+        } else if (isHbmCurrentlyAllowed()) {
             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
         }
 
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index e6bf2c9..3fafca8 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -266,7 +266,11 @@
                 throw new RuntimeException(e);
             }
         }
+        updateConfiguration(grammaticalGender, userId);
+        Trace.endSection();
+    }
 
+    private void updateConfiguration(int grammaticalGender, int userId) {
         try {
             Configuration config = new Configuration();
             int preValue = config.getGrammaticalGender();
@@ -280,7 +284,6 @@
         } catch (RemoteException e) {
             Log.w(TAG, "Can not update configuration", e);
         }
-        Trace.endSection();
     }
 
     public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
@@ -372,7 +375,9 @@
                 if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
                     try (FileInputStream in = new FileInputStream(file)) {
                         final TypedXmlPullParser parser = Xml.resolvePullParser(in);
-                        mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+                        int grammaticalGender = getGrammaticalGenderFromXml(parser);
+                        mGrammaticalGenderCache.put(userId, grammaticalGender);
+                        updateConfiguration(grammaticalGender, userId);
                     } catch (IOException | XmlPullParserException e) {
                         Log.e(TAG, "Failed to parse XML configuration from " + file, e);
                     }
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da..dd6433d 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Handler;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 
@@ -67,7 +69,7 @@
         AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
     }
 
-    static void initialize(@NonNull Handler handler) {
+    static void initialize(@NonNull Handler handler, @NonNull Context context) {
         final UserManagerInternal userManagerInternal =
                 LocalServices.getService(UserManagerInternal.class);
         handler.post(() -> {
@@ -79,8 +81,16 @@
                             handler.post(() -> {
                                 synchronized (ImfLock.class) {
                                     if (!sPerUserMap.contains(userId)) {
-                                        sPerUserMap.put(userId,
-                                                AdditionalSubtypeUtils.load(userId));
+                                        final AdditionalSubtypeMap additionalSubtypeMap =
+                                                AdditionalSubtypeUtils.load(userId);
+                                        sPerUserMap.put(userId, additionalSubtypeMap);
+                                        final InputMethodSettings settings =
+                                                InputMethodManagerService
+                                                        .queryInputMethodServicesInternal(context,
+                                                                userId,
+                                                                additionalSubtypeMap,
+                                                                DirectBootAwareness.AUTO);
+                                        InputMethodSettingsRepository.put(userId, settings);
                                     }
                                 }
                             });
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fef5661..50d2c63 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -284,9 +284,12 @@
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
-    @NonNull
+
     @MultiUserUnawareField
-    private InputMethodSettings mSettings;
+    @UserIdInt
+    @GuardedBy("ImfLock.class")
+    private int mCurrentUserId;
+
     @MultiUserUnawareField
     final SettingsObserver mSettingsObserver;
     final WindowManagerInternal mWindowManagerInternal;
@@ -491,7 +494,7 @@
     @GuardedBy("ImfLock.class")
     @Nullable
     InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
-        return mSettings.getMethodMap().get(imeId);
+        return InputMethodSettingsRepository.get(mCurrentUserId).getMethodMap().get(imeId);
     }
 
     /**
@@ -809,7 +812,8 @@
                     InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
                 } else {
                     boolean enabledChanged = false;
-                    String newEnabled = mSettings.getEnabledInputMethodsStr();
+                    String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId)
+                            .getEnabledInputMethodsStr();
                     if (!mLastEnabled.equals(newEnabled)) {
                         mLastEnabled = newEnabled;
                         enabledChanged = true;
@@ -841,9 +845,11 @@
                 // sender userId can be a real user ID or USER_ALL.
                 final int senderUserId = pendingResult.getSendingUserId();
                 if (senderUserId != UserHandle.USER_ALL) {
-                    if (senderUserId != mSettings.getUserId()) {
-                        // A background user is trying to hide the dialog. Ignore.
-                        return;
+                    synchronized (ImfLock.class) {
+                        if (senderUserId != mCurrentUserId) {
+                            // A background user is trying to hide the dialog. Ignore.
+                            return;
+                        }
                     }
                 }
                 mMenuController.hideInputMethodMenu();
@@ -867,7 +873,15 @@
             if (!mSystemReady) {
                 return;
             }
-            buildInputMethodListLocked(true);
+            for (int userId : mUserManagerInternal.getUserIds()) {
+                final InputMethodSettings settings = queryInputMethodServicesInternal(
+                                mContext,
+                                userId,
+                                AdditionalSubtypeMapRepository.get(userId),
+                                DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, settings);
+            }
+            postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
             // If the locale is changed, needs to reset the default ime
             resetDefaultImeLocked(mContext);
             updateFromSettingsLocked(true);
@@ -927,7 +941,7 @@
         @GuardedBy("ImfLock.class")
         private boolean isChangingPackagesOfCurrentUserLocked() {
             final int userId = getChangingUserId();
-            final boolean retval = userId == mSettings.getUserId();
+            final boolean retval = userId == mCurrentUserId;
             if (DEBUG) {
                 if (!retval) {
                     Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
@@ -942,8 +956,10 @@
                 if (!isChangingPackagesOfCurrentUserLocked()) {
                     return false;
                 }
-                String curInputMethodId = mSettings.getSelectedInputMethod();
-                final List<InputMethodInfo> methodList = mSettings.getMethodList();
+                final InputMethodSettings settings =
+                        InputMethodSettingsRepository.get(mCurrentUserId);
+                String curInputMethodId = settings.getSelectedInputMethod();
+                final List<InputMethodInfo> methodList = settings.getMethodList();
                 final int numImes = methodList.size();
                 if (curInputMethodId != null) {
                     for (int i = 0; i < numImes; i++) {
@@ -1060,16 +1076,10 @@
         private void onFinishPackageChangesInternal() {
             synchronized (ImfLock.class) {
                 final int userId = getChangingUserId();
-                final boolean isCurrentUser = (userId == mSettings.getUserId());
+                final boolean isCurrentUser = (userId == mCurrentUserId);
                 final AdditionalSubtypeMap additionalSubtypeMap =
                         AdditionalSubtypeMapRepository.get(userId);
-                final InputMethodSettings settings;
-                if (isCurrentUser) {
-                    settings = mSettings;
-                } else {
-                    settings = queryInputMethodServicesInternal(mContext, userId,
-                            additionalSubtypeMap, DirectBootAwareness.AUTO);
-                }
+                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
 
                 InputMethodInfo curIm = null;
                 String curInputMethodId = settings.getSelectedInputMethod();
@@ -1113,13 +1123,18 @@
                     AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
                             settings.getMethodMap());
                 }
-
-                if (!isCurrentUser
-                        || !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+                if (isCurrentUser
+                        && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
                     return;
                 }
 
-                buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, newSettings);
+                if (!isCurrentUser) {
+                    return;
+                }
+                postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
 
                 boolean changed = false;
 
@@ -1271,17 +1286,18 @@
 
     void onUnlockUser(@UserIdInt int userId) {
         synchronized (ImfLock.class) {
-            final int currentUserId = mSettings.getUserId();
             if (DEBUG) {
-                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
+                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
             }
-            if (userId != currentUserId) {
+            if (!mSystemReady) {
                 return;
             }
-            mSettings = InputMethodSettings.createEmptyMap(userId);
-            if (mSystemReady) {
+            final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                    userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+            InputMethodSettingsRepository.put(userId, newSettings);
+            if (mCurrentUserId == userId) {
                 // We need to rebuild IMEs.
-                buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                 updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
             }
         }
@@ -1347,19 +1363,20 @@
 
         mShowOngoingImeSwitcherForPhones = false;
 
-        AdditionalSubtypeMapRepository.initialize(mHandler);
+        // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+        InputMethodSettingsRepository.initialize(mHandler, mContext);
+        AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
 
-        final int userId = mActivityManagerInternal.getCurrentUserId();
+        mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
 
-        // mSettings should be created before buildInputMethodListLocked
-        mSettings = InputMethodSettings.createEmptyMap(userId);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
 
         mSwitchingController =
                 InputMethodSubtypeSwitchingController.createInstanceLocked(context,
-                        mSettings.getMethodMap(), userId);
+                        settings.getMethodMap(), settings.getUserId());
         mHardwareKeyboardShortcutController =
-                new HardwareKeyboardShortcutController(mSettings.getMethodMap(),
-                        mSettings.getUserId());
+                new HardwareKeyboardShortcutController(settings.getMethodMap(),
+                        settings.getUserId());
         mMenuController = new InputMethodMenuController(this);
         mBindingController =
                 bindingControllerForTesting != null
@@ -1392,7 +1409,7 @@
     @GuardedBy("ImfLock.class")
     @UserIdInt
     int getCurrentImeUserIdLocked() {
-        return mSettings.getUserId();
+        return mCurrentUserId;
     }
 
     private final class InkWindowInitializer implements Runnable {
@@ -1428,12 +1445,13 @@
     private void resetDefaultImeLocked(Context context) {
         // Do not reset the default (current) IME when it is a 3rd-party IME
         String selectedMethodId = getSelectedMethodIdLocked();
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         if (selectedMethodId != null
-                && !mSettings.getMethodMap().get(selectedMethodId).isSystem()) {
+                && !settings.getMethodMap().get(selectedMethodId).isSystem()) {
             return;
         }
         final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
-                context, mSettings.getEnabledInputMethodList());
+                context, settings.getEnabledInputMethodList());
         if (suitableImes.isEmpty()) {
             Slog.i(TAG, "No default found");
             return;
@@ -1489,7 +1507,7 @@
             IInputMethodClientInvoker clientToBeReset) {
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
-                    + " currentUserId=" + mSettings.getUserId());
+                    + " currentUserId=" + mCurrentUserId);
         }
 
         maybeInitImeNavbarConfigLocked(newUserId);
@@ -1497,8 +1515,9 @@
         // ContentObserver should be registered again when the user is changed
         mSettingsObserver.registerContentObserverLocked(newUserId);
 
-        mSettings = InputMethodSettings.createEmptyMap(newUserId);
-        final String defaultImiId = mSettings.getSelectedInputMethod();
+        mCurrentUserId = newUserId;
+        final String defaultImiId = SecureSettingsWrapper.getString(
+                Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
 
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
@@ -1515,8 +1534,10 @@
         // The mSystemReady flag is set during boot phase,
         // and user switch would not happen at that time.
         resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
-        buildInputMethodListLocked(initialUserSwitch);
-        if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
+
+        final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
+        postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
+        if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) {
             // This is the first time of the user switch and
             // set the current ime to the proper one.
             resetDefaultImeLocked(mContext);
@@ -1526,12 +1547,12 @@
         if (initialUserSwitch) {
             InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                     getPackageManagerForUser(mContext, newUserId),
-                    mSettings.getEnabledInputMethodList());
+                    newSettings.getEnabledInputMethodList());
         }
 
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
-                    + " selectedIme=" + mSettings.getSelectedInputMethod());
+                    + " selectedIme=" + newSettings.getSelectedInputMethod());
         }
 
         if (mIsInteractive && clientToBeReset != null) {
@@ -1554,7 +1575,7 @@
             }
             if (!mSystemReady) {
                 mSystemReady = true;
-                final int currentUserId = mSettings.getUserId();
+                final int currentUserId = mCurrentUserId;
                 mStatusBarManagerInternal =
                         LocalServices.getService(StatusBarManagerInternal.class);
                 hideStatusBarIconLocked();
@@ -1575,7 +1596,7 @@
                     // the "mImeDrawsImeNavBarResLazyInitFuture" field.
                     synchronized (ImfLock.class) {
                         mImeDrawsImeNavBarResLazyInitFuture = null;
-                        if (currentUserId != mSettings.getUserId()) {
+                        if (currentUserId != mCurrentUserId) {
                             // This means that the current user is already switched to other user
                             // before the background task is executed. In this scenario the relevant
                             // field should already be initialized.
@@ -1594,13 +1615,19 @@
                         UserHandle.ALL, broadcastFilterForAllUsers, null, null,
                         Context.RECEIVER_EXPORTED);
 
-                final String defaultImiId = mSettings.getSelectedInputMethod();
+                final String defaultImiId = SecureSettingsWrapper.getString(
+                        Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId);
                 final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
-                buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
+                        DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(currentUserId, newSettings);
+                postInputMethodSettingUpdatedLocked(
+                        !imeSelectedOnBoot /* resetDefaultEnabledIme */);
                 updateFromSettingsLocked(true);
                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                         getPackageManagerForUser(mContext, currentUserId),
-                        mSettings.getEnabledInputMethodList());
+                        newSettings.getEnabledInputMethodList());
             }
         }
     }
@@ -1647,7 +1674,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mSettings.getUserId(), null);
+                    mCurrentUserId, null);
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
@@ -1670,7 +1697,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mSettings.getUserId(), null);
+                    mCurrentUserId, null);
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
@@ -1698,14 +1725,12 @@
             }
 
             // Check if selected IME of current user supports handwriting.
-            if (userId == mSettings.getUserId()) {
+            if (userId == mCurrentUserId) {
                 return mBindingController.supportsStylusHandwriting()
                         && (!connectionless
                                 || mBindingController.supportsConnectionlessStylusHandwriting());
             }
-            //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
-            //TODO(b/210039666): use cache.
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final InputMethodInfo imi = settings.getMethodMap().get(
                     settings.getSelectedInputMethod());
             return imi != null && imi.supportsStylusHandwriting()
@@ -1729,9 +1754,8 @@
     private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
             @DirectBootAwareness int directBootAwareness, int callingUid) {
         final InputMethodSettings settings;
-        if (userId == mSettings.getUserId()
-                && directBootAwareness == DirectBootAwareness.AUTO) {
-            settings = mSettings;
+        if (directBootAwareness == DirectBootAwareness.AUTO) {
+            settings = InputMethodSettingsRepository.get(userId);
         } else {
             final AdditionalSubtypeMap additionalSubtypeMap =
                     AdditionalSubtypeMapRepository.get(userId);
@@ -1749,15 +1773,8 @@
     @GuardedBy("ImfLock.class")
     private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
             int callingUid) {
-        final ArrayList<InputMethodInfo> methodList;
-        final InputMethodSettings settings;
-        if (userId == mSettings.getUserId()) {
-            methodList = mSettings.getEnabledInputMethodList();
-            settings = mSettings;
-        } else {
-            settings = queryMethodMapForUserLocked(userId);
-            methodList = settings.getEnabledInputMethodList();
-        }
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList();
         // filter caller's access to input methods
         methodList.removeIf(imi ->
                 !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
@@ -1815,22 +1832,7 @@
     @GuardedBy("ImfLock.class")
     private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
             boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
-        if (userId == mSettings.getUserId()) {
-            final InputMethodInfo imi;
-            String selectedMethodId = getSelectedMethodIdLocked();
-            if (imiId == null && selectedMethodId != null) {
-                imi = mSettings.getMethodMap().get(selectedMethodId);
-            } else {
-                imi = mSettings.getMethodMap().get(imiId);
-            }
-            if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, mSettings)) {
-                return Collections.emptyList();
-            }
-            return mSettings.getEnabledInputMethodSubtypeList(
-                    imi, allowsImplicitlyEnabledSubtypes);
-        }
-        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         final InputMethodInfo imi = settings.getMethodMap().get(imiId);
         if (imi == null) {
             return Collections.emptyList();
@@ -2010,7 +2012,7 @@
 
         final boolean restarting = !initial;
         final Binder startInputToken = new Binder();
-        final StartInputInfo info = new StartInputInfo(mSettings.getUserId(),
+        final StartInputInfo info = new StartInputInfo(mCurrentUserId,
                 getCurTokenLocked(),
                 mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
                 UserHandle.getUserId(mCurClient.mUid),
@@ -2024,9 +2026,9 @@
         // same-user scenarios.
         // That said ignoring cross-user scenario will never affect IMEs that do not have
         // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
-        if (mSettings.getUserId() == UserHandle.getUserId(
+        if (mCurrentUserId == UserHandle.getUserId(
                 mCurClient.mUid)) {
-            mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(),
+            mPackageManagerInternal.grantImplicitAccess(mCurrentUserId,
                     null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
                     mCurClient.mUid, true /* direct */);
         }
@@ -2049,7 +2051,8 @@
         }
 
         String curId = getCurIdLocked();
-        final InputMethodInfo curInputMethodInfo = mSettings.getMethodMap().get(curId);
+        final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
+                .getMethodMap().get(curId);
         final boolean suppressesSpellChecker =
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
@@ -2229,17 +2232,18 @@
             return currentMethodId;
         }
 
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final int oldDeviceId = mDeviceIdToShowIme;
         mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme);
         if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
             if (oldDeviceId == DEVICE_ID_DEFAULT) {
                 return currentMethodId;
             }
-            final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod();
+            final String defaultDeviceMethodId = settings.getSelectedDefaultDeviceInputMethod();
             if (DEBUG) {
                 Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
             }
-            mSettings.putSelectedDefaultDeviceInputMethod(null);
+            settings.putSelectedDefaultDeviceInputMethod(null);
             return defaultDeviceMethodId;
         }
 
@@ -2247,7 +2251,7 @@
                 mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId);
         if (Objects.equals(deviceMethodId, currentMethodId)) {
             return currentMethodId;
-        } else if (!mSettings.getMethodMap().containsKey(deviceMethodId)) {
+        } else if (!settings.getMethodMap().containsKey(deviceMethodId)) {
             if (DEBUG) {
                 Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme
                         + " because its custom input method is not available: " + deviceMethodId);
@@ -2259,7 +2263,7 @@
             if (DEBUG) {
                 Slog.v(TAG, "Storing default device input method " + currentMethodId);
             }
-            mSettings.putSelectedDefaultDeviceInputMethod(currentMethodId);
+            settings.putSelectedDefaultDeviceInputMethod(currentMethodId);
         }
         if (DEBUG) {
             Slog.v(TAG, "Switching current input method from " + currentMethodId
@@ -2289,7 +2293,8 @@
         if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
             return false;
         }
-        final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+        final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+                .getMethodMap().get(selectedMethodId);
         if (imi == null) {
             return false;
         }
@@ -2633,7 +2638,7 @@
                 } else if (packageName != null) {
                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
                     final PackageManager userAwarePackageManager =
-                            getPackageManagerForUser(mContext, mSettings.getUserId());
+                            getPackageManagerForUser(mContext, mCurrentUserId);
                     ApplicationInfo applicationInfo = null;
                     try {
                         applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
@@ -2695,7 +2700,7 @@
             return false;
         }
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
-                && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) {
+                && mWindowManagerInternal.isKeyguardSecure(mCurrentUserId)) {
             return false;
         }
         if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -2712,7 +2717,8 @@
             return false;
         }
 
-        List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter(
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter(
                 InputMethodInfo::shouldShowInInputMethodPicker);
         final int numImes = imes.size();
         if (numImes > 2) return true;
@@ -2724,7 +2730,7 @@
         for (int i = 0; i < numImes; ++i) {
             final InputMethodInfo imi = imes.get(i);
             final List<InputMethodSubtype> subtypes =
-                    mSettings.getEnabledInputMethodSubtypeList(imi, true);
+                    settings.getEnabledInputMethodSubtypeList(imi, true);
             final int subtypeCount = subtypes.size();
             if (subtypeCount == 0) {
                 ++nonAuxCount;
@@ -2872,11 +2878,12 @@
 
     @GuardedBy("ImfLock.class")
     void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         if (enabledMayChange) {
             final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
-                    mSettings.getUserId());
+                    settings.getUserId());
 
-            List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+            List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
             for (int i = 0; i < enabled.size(); i++) {
                 // We allow the user to select "disabled until used" apps, so if they
                 // are enabling one of those here we now need to make it enabled.
@@ -2903,20 +2910,20 @@
 
         if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
             String ime = SecureSettingsWrapper.getString(
-                    Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+                    Settings.Secure.DEFAULT_INPUT_METHOD, null, settings.getUserId());
             String defaultDeviceIme = SecureSettingsWrapper.getString(
-                    Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+                    Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
             if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
                 if (DEBUG) {
                     Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
-                            + " device input method for user " + mSettings.getUserId()
+                            + " device input method for user " + settings.getUserId()
                             + " - restoring " + defaultDeviceIme);
                 }
                 SecureSettingsWrapper.putString(
                         Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
-                        mSettings.getUserId());
+                        settings.getUserId());
                 SecureSettingsWrapper.putString(
-                        Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+                        Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
             }
         }
 
@@ -2924,14 +2931,14 @@
         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
         // enabled.
-        String id = mSettings.getSelectedInputMethod();
+        String id = settings.getSelectedInputMethod();
         // There is no input method selected, try to choose new applicable input method.
         if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
-            id = mSettings.getSelectedInputMethod();
+            id = settings.getSelectedInputMethod();
         }
         if (!TextUtils.isEmpty(id)) {
             try {
-                setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
+                setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id));
             } catch (IllegalArgumentException e) {
                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
                 resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED);
@@ -2942,18 +2949,18 @@
         }
 
         // TODO: Instantiate mSwitchingController for each user.
-        if (mSettings.getUserId() == mSwitchingController.getUserId()) {
-            mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+        if (settings.getUserId() == mSwitchingController.getUserId()) {
+            mSwitchingController.resetCircularListLocked(settings.getMethodMap());
         } else {
             mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
-                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
+                    mContext, settings.getMethodMap(), settings.getUserId());
         }
         // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
-        if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
-            mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+        if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+            mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
         } else {
             mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
-                    mSettings.getMethodMap(), mSettings.getUserId());
+                    settings.getMethodMap(), settings.getUserId());
         }
         sendOnNavButtonFlagsChangedLocked();
     }
@@ -2977,14 +2984,15 @@
 
     @GuardedBy("ImfLock.class")
     void setInputMethodLocked(String id, int subtypeId, int deviceId) {
-        InputMethodInfo info = mSettings.getMethodMap().get(id);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        InputMethodInfo info = settings.getMethodMap().get(id);
         if (info == null) {
             throw getExceptionForUnknownImeId(id);
         }
 
         // See if we need to notify a subtype change within the same IME.
         if (id.equals(getSelectedMethodIdLocked())) {
-            final int userId = mSettings.getUserId();
+            final int userId = settings.getUserId();
             final int subtypeCount = info.getSubtypeCount();
             if (subtypeCount <= 0) {
                 notifyInputMethodSubtypeChangedLocked(userId, info, null);
@@ -3025,7 +3033,7 @@
             // method is a custom one specific to a virtual device. So only update the settings
             // entry used to restore the default device input method once we want to show the IME
             // back on the default device.
-            mSettings.putSelectedDefaultDeviceInputMethod(id);
+            settings.putSelectedDefaultDeviceInputMethod(id);
             return;
         }
         IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -3535,7 +3543,7 @@
                             return InputBindResult.USER_SWITCHING;
                         }
                         final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
-                                mSettings.getUserId(), false /* enabledOnly */);
+                                mCurrentUserId, false /* enabledOnly */);
                         for (int profileId : profileIdsWithDisabled) {
                             if (profileId == userId) {
                                 scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3581,10 +3589,10 @@
                     }
 
                     // Verify if caller is a background user.
-                    final int currentUserId = mSettings.getUserId();
-                    if (userId != currentUserId) {
+                    if (userId != mCurrentUserId) {
                         if (ArrayUtils.contains(
-                                mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
+                                mUserManagerInternal.getProfileIds(mCurrentUserId, false),
+                                userId)) {
                             // cross-profile access is always allowed here to allow
                             // profile-switching.
                             scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3773,7 +3781,7 @@
                 && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
             return true;
         }
-        if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
+        if (mCurrentUserId != UserHandle.getUserId(uid)) {
             return false;
         }
         if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
@@ -3841,9 +3849,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
-            final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final InputMethodInfo imi = settings.getMethodMap().get(id);
             if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, mSettings)) {
+                    imi.getPackageName(), callingUid, userId, settings)) {
                 throw getExceptionForUnknownImeId(id);
             }
             setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
@@ -3859,9 +3868,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
-            final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final InputMethodInfo imi = settings.getMethodMap().get(id);
             if (imi == null || !canCallerAccessInputMethod(
-                    imi.getPackageName(), callingUid, userId, mSettings)) {
+                    imi.getPackageName(), callingUid, userId, settings)) {
                 throw getExceptionForUnknownImeId(id);
             }
             if (subtype != null) {
@@ -3879,10 +3889,11 @@
             if (!calledWithValidTokenLocked(token)) {
                 return false;
             }
-            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype();
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
             final InputMethodInfo lastImi;
             if (lastIme != null) {
-                lastImi = mSettings.getMethodMap().get(lastIme.first);
+                lastImi = settings.getMethodMap().get(lastIme.first);
             } else {
                 lastImi = null;
             }
@@ -3906,7 +3917,7 @@
                 // This is a safety net. If the currentSubtype can't be added to the history
                 // and the framework couldn't find the last ime, we will make the last ime be
                 // the most applicable enabled keyboard subtype of the system imes.
-                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+                final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
                 if (enabled != null) {
                     final int enabledCount = enabled.size();
                     final String locale;
@@ -3914,7 +3925,7 @@
                             && !TextUtils.isEmpty(mCurrentSubtype.getLocale())) {
                         locale = mCurrentSubtype.getLocale();
                     } else {
-                        locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString();
+                        locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
                     }
                     for (int i = 0; i < enabledCount; ++i) {
                         final InputMethodInfo imi = enabled.get(i);
@@ -3961,8 +3972,9 @@
 
     @GuardedBy("ImfLock.class")
     private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                onlyCurrentIme, mSettings.getMethodMap().get(getSelectedMethodIdLocked()),
+                onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()),
                 mCurrentSubtype);
         if (nextSubtype == null) {
             return false;
@@ -3978,9 +3990,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return false;
             }
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
                     false /* onlyCurrentIme */,
-                    mSettings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
+                    settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
             return nextSubtype != null;
         }
     }
@@ -3992,12 +4005,7 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mSettings.getUserId() == userId) {
-                return mSettings.getLastInputMethodSubtype();
-            }
-
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
-            return settings.getLastInputMethodSubtype();
+            return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
         }
     }
 
@@ -4028,20 +4036,21 @@
             }
 
             final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
-            final boolean isCurrentUser = (mSettings.getUserId() == userId);
-            final InputMethodSettings settings = isCurrentUser
-                    ? mSettings
-                    : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                            DirectBootAwareness.AUTO);
+            final boolean isCurrentUser = (mCurrentUserId == userId);
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
             final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
                     imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
             if (additionalSubtypeMap != newAdditionalSubtypeMap) {
                 AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
                         settings.getMethodMap());
+                final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+                        userId, AdditionalSubtypeMapRepository.get(userId),
+                        DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, newSettings);
                 if (isCurrentUser) {
                     final long ident = Binder.clearCallingIdentity();
                     try {
-                        buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                        postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
                     } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
@@ -4070,9 +4079,8 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (ImfLock.class) {
-                final boolean currentUser = (mSettings.getUserId() == userId);
-                final InputMethodSettings settings = currentUser
-                        ? mSettings : queryMethodMapForUserLocked(userId);
+                final boolean currentUser = (mCurrentUserId == userId);
+                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
                 if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
                     return;
                 }
@@ -4422,11 +4430,11 @@
                 }
                 return;
             }
-            if (mSettings.getUserId() != mSwitchingController.getUserId()) {
+            if (mCurrentUserId != mSwitchingController.getUserId()) {
                 return;
             }
-            final InputMethodInfo imi =
-                    mSettings.getMethodMap().get(getSelectedMethodIdLocked());
+            final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+                    .getMethodMap().get(getSelectedMethodIdLocked());
             if (imi != null) {
                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
             }
@@ -4486,8 +4494,9 @@
             return;
         } else {
             // Called with current IME's token.
-            if (mSettings.getMethodMap().get(id) != null
-                    && mSettings.getEnabledInputMethodListWithFilter(
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+            if (settings.getMethodMap().get(id) != null
+                    && settings.getEnabledInputMethodListWithFilter(
                             (info) -> info.getId().equals(id)).isEmpty()) {
                 throw new IllegalStateException("Requested IME is not enabled: " + id);
             }
@@ -4666,21 +4675,23 @@
                         return false;
                 }
                 synchronized (ImfLock.class) {
+                    final InputMethodSettings settings =
+                            InputMethodSettingsRepository.get(mCurrentUserId);
                     final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
-                            && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId());
-                    final String lastInputMethodId = mSettings.getSelectedInputMethod();
+                            && mWindowManagerInternal.isKeyguardSecure(settings.getUserId());
+                    final String lastInputMethodId = settings.getSelectedInputMethod();
                     int lastInputMethodSubtypeId =
-                            mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+                            settings.getSelectedInputMethodSubtypeId(lastInputMethodId);
 
                     final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
                             .getSortedInputMethodAndSubtypeList(
                                     showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
-                                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
+                                    mContext, settings.getMethodMap(), settings.getUserId());
                     if (imList.isEmpty()) {
                         Slog.w(TAG, "Show switching menu failed, imList is empty,"
                                 + " showAuxSubtypes: " + showAuxSubtypes
                                 + " isScreenLocked: " + isScreenLocked
-                                + " userId: " + mSettings.getUserId());
+                                + " userId: " + settings.getUserId());
                         return false;
                     }
 
@@ -4866,8 +4877,9 @@
 
     @GuardedBy("ImfLock.class")
     private boolean chooseNewDefaultIMELocked() {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
-                mSettings.getEnabledInputMethodList());
+                settings.getEnabledInputMethodList());
         if (imi != null) {
             if (DEBUG) {
                 Slog.d(TAG, "New default IME was selected: " + imi.getId());
@@ -4969,7 +4981,7 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
+    void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme) {
         if (DEBUG) {
             Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
                     + " \n ------ caller=" + Debug.getCallers(10));
@@ -4981,9 +4993,7 @@
         mMethodMapUpdateCount++;
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
-        mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
-                AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
-                DirectBootAwareness.AUTO);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
 
         // Construct the set of possible IME packages for onPackageChanged() to avoid false
         // negatives when the package state remains to be the same but only the component state is
@@ -4995,7 +5005,7 @@
             final List<ResolveInfo> allInputMethodServices =
                     mContext.getPackageManager().queryIntentServicesAsUser(
                             new Intent(InputMethod.SERVICE_INTERFACE),
-                            PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId());
+                            PackageManager.MATCH_DISABLED_COMPONENTS, settings.getUserId());
             final int numImes = allInputMethodServices.size();
             for (int i = 0; i < numImes; ++i) {
                 final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
@@ -5010,11 +5020,11 @@
         if (!resetDefaultEnabledIme) {
             boolean enabledImeFound = false;
             boolean enabledNonAuxImeFound = false;
-            final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList();
+            final List<InputMethodInfo> enabledImes = settings.getEnabledInputMethodList();
             final int numImes = enabledImes.size();
             for (int i = 0; i < numImes; ++i) {
                 final InputMethodInfo imi = enabledImes.get(i);
-                if (mSettings.getMethodMap().containsKey(imi.getId())) {
+                if (settings.getMethodMap().containsKey(imi.getId())) {
                     enabledImeFound = true;
                     if (!imi.isAuxiliaryIme()) {
                         enabledNonAuxImeFound = true;
@@ -5038,7 +5048,7 @@
 
         if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
             final ArrayList<InputMethodInfo> defaultEnabledIme =
-                    InputMethodInfoUtils.getDefaultEnabledImes(mContext, mSettings.getMethodList(),
+                    InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList(),
                             reenableMinimumNonAuxSystemImes);
             final int numImes = defaultEnabledIme.size();
             for (int i = 0; i < numImes; ++i) {
@@ -5050,9 +5060,9 @@
             }
         }
 
-        final String defaultImiId = mSettings.getSelectedInputMethod();
+        final String defaultImiId = settings.getSelectedInputMethod();
         if (!TextUtils.isEmpty(defaultImiId)) {
-            if (!mSettings.getMethodMap().containsKey(defaultImiId)) {
+            if (!settings.getMethodMap().containsKey(defaultImiId)) {
                 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
                 if (chooseNewDefaultIMELocked()) {
                     updateInputMethodsFromSettingsLocked(true);
@@ -5066,26 +5076,26 @@
         updateDefaultVoiceImeIfNeededLocked();
 
         // TODO: Instantiate mSwitchingController for each user.
-        if (mSettings.getUserId() == mSwitchingController.getUserId()) {
-            mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+        if (settings.getUserId() == mSwitchingController.getUserId()) {
+            mSwitchingController.resetCircularListLocked(settings.getMethodMap());
         } else {
             mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
-                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
+                    mContext, settings.getMethodMap(), mCurrentUserId);
         }
         // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
-        if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
-            mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+        if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+            mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
         } else {
             mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
-                    mSettings.getMethodMap(), mSettings.getUserId());
+                    settings.getMethodMap(), settings.getUserId());
         }
 
         sendOnNavButtonFlagsChangedLocked();
 
         // Notify InputMethodListListeners of the new installed InputMethods.
-        final List<InputMethodInfo> inputMethodList = mSettings.getMethodList();
+        final List<InputMethodInfo> inputMethodList = settings.getMethodList();
         mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
-                mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
+                settings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
     }
 
     @GuardedBy("ImfLock.class")
@@ -5100,11 +5110,12 @@
 
     @GuardedBy("ImfLock.class")
     private void updateDefaultVoiceImeIfNeededLocked() {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         final String systemSpeechRecognizer =
                 mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
-        final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
+        final String currentDefaultVoiceImeId = settings.getDefaultVoiceInputMethod();
         final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme(
-                mSettings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
+                settings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
         if (newSystemVoiceIme == null) {
             if (DEBUG) {
                 Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked,"
@@ -5113,7 +5124,7 @@
             // Clear DEFAULT_VOICE_INPUT_METHOD when necessary.  Note that InputMethodSettings
             // does not update the actual Secure Settings until the user is unlocked.
             if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) {
-                mSettings.putDefaultVoiceInputMethod("");
+                settings.putDefaultVoiceInputMethod("");
                 // We don't support disabling the voice ime when a package is removed from the
                 // config.
             }
@@ -5126,7 +5137,7 @@
             Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme);
         }
         setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true);
-        mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
+        settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
     }
 
     // ----------------------------------------------------------------------
@@ -5141,8 +5152,9 @@
      */
     @GuardedBy("ImfLock.class")
     private boolean setInputMethodEnabledLocked(String id, boolean enabled) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
         if (enabled) {
-            final String enabledImeIdsStr = mSettings.getEnabledInputMethodsStr();
+            final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
             final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
                     enabledImeIdsStr, id);
             if (TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) {
@@ -5150,29 +5162,29 @@
                 // Nothing to do. The previous state was enabled.
                 return true;
             }
-            mSettings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
+            settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
             // Previous state was disabled.
             return false;
         } else {
-            final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+            final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = settings
                     .getEnabledInputMethodsAndSubtypeList();
             StringBuilder builder = new StringBuilder();
-            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId(
+            if (settings.buildAndPutEnabledInputMethodsStrRemovingId(
                     builder, enabledInputMethodsList, id)) {
                 if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
                     // Disabled input method is currently selected, switch to another one.
-                    final String selId = mSettings.getSelectedInputMethod();
+                    final String selId = settings.getSelectedInputMethod();
                     if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
                         Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
                         resetSelectedInputMethodAndSubtypeLocked("");
                     }
-                } else if (id.equals(mSettings.getSelectedDefaultDeviceInputMethod())) {
+                } else if (id.equals(settings.getSelectedDefaultDeviceInputMethod())) {
                     // Disabled default device IME while using a virtual device one, choose a
                     // new default one but only update the settings.
                     InputMethodInfo newDefaultIme =
                             InputMethodInfoUtils.getMostApplicableDefaultIME(
-                                        mSettings.getEnabledInputMethodList());
-                    mSettings.putSelectedDefaultDeviceInputMethod(
+                                    settings.getEnabledInputMethodList());
+                    settings.putSelectedDefaultDeviceInputMethod(
                             newDefaultIme == null ? null : newDefaultIme.getId());
                 }
                 // Previous state was enabled.
@@ -5188,29 +5200,30 @@
     @GuardedBy("ImfLock.class")
     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
             boolean setSubtypeOnly) {
-        mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        settings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
                 mCurrentSubtype);
 
         // Set Subtype here
         if (imi == null || subtypeId < 0) {
-            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+            settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
             mCurrentSubtype = null;
         } else {
             if (subtypeId < imi.getSubtypeCount()) {
                 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
-                mSettings.putSelectedSubtype(subtype.hashCode());
+                settings.putSelectedSubtype(subtype.hashCode());
                 mCurrentSubtype = subtype;
             } else {
-                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+                settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
                 // If the subtype is not specified, choose the most applicable one
                 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
             }
         }
-        notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype);
+        notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, mCurrentSubtype);
 
         if (!setSubtypeOnly) {
             // Set InputMethod here
-            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+            settings.putSelectedInputMethod(imi != null ? imi.getId() : "");
         }
     }
 
@@ -5218,13 +5231,15 @@
     private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
         mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
         mDisplayIdToShowIme = INVALID_DISPLAY;
-        mSettings.putSelectedDefaultDeviceInputMethod(null);
 
-        InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        settings.putSelectedDefaultDeviceInputMethod(null);
+
+        InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
         int lastSubtypeId = NOT_A_SUBTYPE_ID;
         // newDefaultIme is empty when there is no candidate for the selected IME.
         if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
-            String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme);
+            String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme);
             if (subtypeHashCode != null) {
                 try {
                     lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
@@ -5251,12 +5266,12 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mSettings.getUserId() == userId) {
+            if (mCurrentUserId == userId) {
                 return getCurrentInputMethodSubtypeLocked();
             }
 
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
-            return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+            return InputMethodSettingsRepository.get(userId)
+                    .getCurrentInputMethodSubtypeForNonCurrentUsers();
         }
     }
 
@@ -5276,26 +5291,27 @@
         if (selectedMethodId == null) {
             return null;
         }
-        final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
-        final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+        final boolean subtypeIsSelected = settings.isSubtypeSelected();
+        final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
         if (imi == null || imi.getSubtypeCount() == 0) {
             return null;
         }
         if (!subtypeIsSelected || mCurrentSubtype == null
                 || !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
-            int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId);
+            int subtypeId = settings.getSelectedInputMethodSubtypeId(selectedMethodId);
             if (subtypeId == NOT_A_SUBTYPE_ID) {
                 // If there are no selected subtypes, the framework will try to find
                 // the most applicable subtype from explicitly or implicitly enabled
                 // subtypes.
                 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
-                        mSettings.getEnabledInputMethodSubtypeList(imi, true);
+                        settings.getEnabledInputMethodSubtypeList(imi, true);
                 // If there is only one explicitly or implicitly enabled subtype,
                 // just returns it.
                 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
                     mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
                 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
-                    final String locale = SystemLocaleWrapper.get(mSettings.getUserId())
+                    final String locale = SystemLocaleWrapper.get(settings.getUserId())
                             .get(0).toString();
                     mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype(
                             explicitlyOrImplicitlyEnabledSubtypes,
@@ -5318,38 +5334,22 @@
      */
     @GuardedBy("ImfLock.class")
     private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
-        final InputMethodSettings settings;
-        if (userId == mSettings.getUserId()) {
-            settings = mSettings;
-        } else {
-            final AdditionalSubtypeMap additionalSubtypeMap =
-                    AdditionalSubtypeMapRepository.get(userId);
-            settings = queryInputMethodServicesInternal(mContext, userId,
-                    additionalSubtypeMap, DirectBootAwareness.AUTO);
-        }
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
         return settings.getMethodMap().get(settings.getSelectedInputMethod());
     }
 
     @GuardedBy("ImfLock.class")
-    private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
-        final AdditionalSubtypeMap additionalSubtypeMap =
-                AdditionalSubtypeMapRepository.get(userId);
-        return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                DirectBootAwareness.AUTO);
-    }
-
-    @GuardedBy("ImfLock.class")
     private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
-        if (userId == mSettings.getUserId()) {
-            if (!mSettings.getMethodMap().containsKey(imeId)
-                    || !mSettings.getEnabledInputMethodList()
-                    .contains(mSettings.getMethodMap().get(imeId))) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        if (userId == mCurrentUserId) {
+            if (!settings.getMethodMap().containsKey(imeId)
+                    || !settings.getEnabledInputMethodList()
+                    .contains(settings.getMethodMap().get(imeId))) {
                 return false; // IME is not found or not enabled.
             }
             setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
             return true;
         }
-        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
         if (!settings.getMethodMap().containsKey(imeId)
                 || !settings.getEnabledInputMethodList().contains(
                         settings.getMethodMap().get(imeId))) {
@@ -5390,8 +5390,9 @@
 
     @GuardedBy("ImfLock.class")
     private void switchKeyboardLayoutLocked(int direction) {
-        final InputMethodInfo currentImi = mSettings.getMethodMap().get(
-                getSelectedMethodIdLocked());
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+
+        final InputMethodInfo currentImi = settings.getMethodMap().get(getSelectedMethodIdLocked());
         if (currentImi == null) {
             return;
         }
@@ -5403,7 +5404,7 @@
         if (nextSubtypeHandle == null) {
             return;
         }
-        final InputMethodInfo nextImi = mSettings.getMethodMap().get(nextSubtypeHandle.getImeId());
+        final InputMethodInfo nextImi = settings.getMethodMap().get(nextSubtypeHandle.getImeId());
         if (nextImi == null) {
             return;
         }
@@ -5482,17 +5483,14 @@
         @Override
         public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                if (userId == mSettings.getUserId()) {
-                    if (!mSettings.getMethodMap().containsKey(imeId)) {
-                        return false; // IME is not found.
-                    }
-                    setInputMethodEnabledLocked(imeId, enabled);
-                    return true;
-                }
-                final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+                final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     return false; // IME is not found.
                 }
+                if (userId == mCurrentUserId) {
+                    setInputMethodEnabledLocked(imeId, enabled);
+                    return true;
+                }
                 if (enabled) {
                     final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
                     final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
@@ -5825,8 +5823,9 @@
         final Printer p = new PrintWriterPrinter(pw);
 
         synchronized (ImfLock.class) {
+            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
             p.println("Current Input Method Manager state:");
-            final List<InputMethodInfo> methodList = mSettings.getMethodList();
+            final List<InputMethodInfo> methodList = settings.getMethodList();
             int numImes = methodList.size();
             p.println("  Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
             for (int i = 0; i < numImes; i++) {
@@ -5877,7 +5876,7 @@
             p.println("  mSwitchingController:");
             mSwitchingController.dump(p);
             p.println("  mSettings:");
-            mSettings.dump(p, "    ");
+            settings.dump(p, "    ");
 
             p.println("  mStartInputHistory:");
             mStartInputHistory.dump(pw, "    ");
@@ -6132,7 +6131,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                    mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                    mCurrentUserId, shellCommand.getErrPrintWriter());
             try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
                 for (int userId : userIds) {
                     final List<InputMethodInfo> methods = all
@@ -6177,7 +6176,7 @@
              PrintWriter error = shellCommand.getErrPrintWriter()) {
             synchronized (ImfLock.class) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                        mCurrentUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6236,14 +6235,14 @@
             PrintWriter error) {
         boolean failedToEnableUnknownIme = false;
         boolean previouslyEnabled = false;
-        if (userId == mSettings.getUserId()) {
-            if (enabled && !mSettings.getMethodMap().containsKey(imeId)) {
+        final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+        if (userId == mCurrentUserId) {
+            if (enabled && !settings.getMethodMap().containsKey(imeId)) {
                 failedToEnableUnknownIme = true;
             } else {
                 previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
             }
         } else {
-            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             if (enabled) {
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     failedToEnableUnknownIme = true;
@@ -6298,7 +6297,7 @@
              PrintWriter error = shellCommand.getErrPrintWriter()) {
             synchronized (ImfLock.class) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                        mCurrentUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6338,7 +6337,7 @@
         synchronized (ImfLock.class) {
             try (PrintWriter out = shellCommand.getOutPrintWriter()) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
+                        mCurrentUserId, shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6350,15 +6349,16 @@
                     }
                     final String nextIme;
                     final List<InputMethodInfo> nextEnabledImes;
-                    if (userId == mSettings.getUserId()) {
+                    final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+                    if (userId == mCurrentUserId) {
                         hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
                         mBindingController.unbindCurrentMethod();
 
                         // Enable default IMEs, disable others
-                        var toDisable = mSettings.getEnabledInputMethodList();
+                        var toDisable = settings.getEnabledInputMethodList();
                         var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
-                                mContext, mSettings.getMethodList());
+                                mContext, settings.getMethodList());
                         toDisable.removeAll(defaultEnabled);
                         for (InputMethodInfo info : toDisable) {
                             setInputMethodEnabledLocked(info.getId(), false);
@@ -6372,16 +6372,11 @@
                         }
                         updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
                         InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
-                                getPackageManagerForUser(mContext, mSettings.getUserId()),
-                                mSettings.getEnabledInputMethodList());
-                        nextIme = mSettings.getSelectedInputMethod();
-                        nextEnabledImes = mSettings.getEnabledInputMethodList();
+                                getPackageManagerForUser(mContext, settings.getUserId()),
+                                settings.getEnabledInputMethodList());
+                        nextIme = settings.getSelectedInputMethod();
+                        nextEnabledImes = settings.getEnabledInputMethodList();
                     } else {
-                        final AdditionalSubtypeMap additionalSubtypeMap =
-                                AdditionalSubtypeMapRepository.get(userId);
-                        final InputMethodSettings settings = queryInputMethodServicesInternal(
-                                mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
-
                         nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
                                 settings.getMethodList());
                         nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 0000000..60b9a4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * 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.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private InputMethodSettingsRepository() {
+    }
+
+    @NonNull
+    @GuardedBy("ImfLock.class")
+    static InputMethodSettings get(@UserIdInt int userId) {
+        final InputMethodSettings obj = sPerUserMap.get(userId);
+        if (obj != null) {
+            return obj;
+        }
+        return InputMethodSettings.createEmptyMap(userId);
+    }
+
+    @GuardedBy("ImfLock.class")
+    static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+        sPerUserMap.put(userId, obj);
+    }
+
+    static void initialize(@NonNull Handler handler, @NonNull Context context) {
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        handler.post(() -> {
+            userManagerInternal.addUserLifecycleListener(
+                    new UserManagerInternal.UserLifecycleListener() {
+                        @Override
+                        public void onUserRemoved(UserInfo user) {
+                            final int userId = user.id;
+                            handler.post(() -> {
+                                synchronized (ImfLock.class) {
+                                    sPerUserMap.remove(userId);
+                                }
+                            });
+                        }
+                    });
+            synchronized (ImfLock.class) {
+                for (int userId : userManagerInternal.getUserIds()) {
+                    final InputMethodSettings settings =
+                            InputMethodManagerService.queryInputMethodServicesInternal(
+                                    context,
+                                    userId,
+                                    AdditionalSubtypeMapRepository.get(userId),
+                                    DirectBootAwareness.AUTO);
+                    sPerUserMap.put(userId, settings);
+                }
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c1a4571e..e80c79a8 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -741,7 +741,7 @@
 
     private static final int MY_UID = Process.myUid();
     private static final int MY_PID = Process.myPid();
-    static final IBinder ALLOWLIST_TOKEN = new Binder();
+    private static final IBinder ALLOWLIST_TOKEN = new Binder();
     protected RankingHandler mRankingHandler;
     private long mLastOverRateLogTime;
     private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
@@ -4875,7 +4875,7 @@
                     // Remove background token before returning notification to untrusted app, this
                     // ensures the app isn't able to perform background operations that are
                     // associated with notification interactions.
-                    notification.overrideAllowlistToken(null);
+                    notification.clearAllowlistToken();
                     return new StatusBarNotification(
                             sbn.getPackageName(),
                             sbn.getOpPkg(),
@@ -7884,8 +7884,6 @@
             }
         }
 
-        notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
-
         // Remote views? Are they too big?
         checkRemoteViews(pkg, tag, id, notification);
     }
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
similarity index 72%
rename from core/java/android/app/ondeviceintelligence/Content.aidl
rename to services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
index 40f0ef9..81f11b5 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package android.app.ondeviceintelligence;
+package com.android.server.ondeviceintelligence;
 
-/**
-  * @hide
-  */
-parcelable Content;
+public interface OnDeviceIntelligenceManagerInternal {
+    String getRemoteServicePackageName();
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 71800ef..28682e3 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,12 +16,11 @@
 
 package com.android.server.ondeviceintelligence;
 
-import static android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED;
-
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.AppGlobals;
-import android.app.ondeviceintelligence.Content;
 import android.app.ondeviceintelligence.DownloadCallback;
 import android.app.ondeviceintelligence.Feature;
 import android.app.ondeviceintelligence.IDownloadCallback;
@@ -33,25 +32,32 @@
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
 import android.app.ondeviceintelligence.ITokenInfoCallback;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.os.Binder;
+import android.content.res.Resources;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
 import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
 import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
 import android.service.ondeviceintelligence.IRemoteProcessingService;
 import android.service.ondeviceintelligence.IRemoteStorageService;
-import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
 import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
 import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
 import android.text.TextUtils;
@@ -62,8 +68,10 @@
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.infra.ServiceConnector;
 import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
+import java.io.FileDescriptor;
 import java.util.Objects;
 import java.util.Set;
 
@@ -84,6 +92,9 @@
     private static final String TAG = OnDeviceIntelligenceManagerService.class.getSimpleName();
     private static final String KEY_SERVICE_ENABLED = "service_enabled";
 
+    /** Handler message to {@link #resetTemporaryServices()} */
+    private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+
     /** Default value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = true;
     private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
@@ -96,19 +107,30 @@
     private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
     volatile boolean mIsServiceEnabled;
 
+    @GuardedBy("mLock")
+    private String[] mTemporaryServiceNames;
+
+    /**
+     * Handler used to reset the temporary service names.
+     */
+    @GuardedBy("mLock")
+    private Handler mTemporaryHandler;
+
     public OnDeviceIntelligenceManagerService(Context context) {
         super(context);
         mContext = context;
+        mTemporaryServiceNames = new String[0];
     }
 
     @Override
     public void onStart() {
         publishBinderService(
-                Context.ON_DEVICE_INTELLIGENCE_SERVICE, new OnDeviceIntelligenceManagerInternal(),
+                Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(),
                 /* allowIsolated = */true);
+        LocalServices.addService(OnDeviceIntelligenceManagerInternal.class,
+                OnDeviceIntelligenceManagerService.this::getRemoteConfiguredPackageName);
     }
 
-
     @Override
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
@@ -133,195 +155,211 @@
                 KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
     }
 
-    private final class OnDeviceIntelligenceManagerInternal extends
-            IOnDeviceIntelligenceManager.Stub {
-        @Override
-        public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
-            Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
-            Objects.requireNonNull(remoteCallback);
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
-            if (!mIsServiceEnabled) {
-                Slog.w(TAG, "Service not available");
-                remoteCallback.sendResult(null);
-                return;
+    private IBinder getOnDeviceIntelligenceManagerService() {
+        return new IOnDeviceIntelligenceManager.Stub() {
+            @Override
+            public String getRemoteServicePackageName() {
+                return OnDeviceIntelligenceManagerService.this.getRemoteConfiguredPackageName();
             }
-            ensureRemoteIntelligenceServiceInitialized();
-            mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.getVersion(remoteCallback));
-        }
 
-        @Override
-        public void getFeature(int id, IFeatureCallback featureCallback) throws RemoteException {
-            Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
-            Objects.requireNonNull(featureCallback);
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
-            if (!mIsServiceEnabled) {
-                Slog.w(TAG, "Service not available");
-                featureCallback.onFailure(
-                        OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
-                        "OnDeviceIntelligenceManagerService is unavailable",
-                        new PersistableBundle());
-                return;
+            @Override
+            public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
+                Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
+                Objects.requireNonNull(remoteCallback);
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+                if (!mIsServiceEnabled) {
+                    Slog.w(TAG, "Service not available");
+                    remoteCallback.sendResult(null);
+                    return;
+                }
+                ensureRemoteIntelligenceServiceInitialized();
+                mRemoteOnDeviceIntelligenceService.run(
+                        service -> service.getVersion(remoteCallback));
             }
-            ensureRemoteIntelligenceServiceInitialized();
-            mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
-        }
 
-        @Override
-        public void listFeatures(IListFeaturesCallback listFeaturesCallback)
-                throws RemoteException {
-            Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
-            Objects.requireNonNull(listFeaturesCallback);
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
-            if (!mIsServiceEnabled) {
-                Slog.w(TAG, "Service not available");
-                listFeaturesCallback.onFailure(
-                        OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
-                        "OnDeviceIntelligenceManagerService is unavailable",
-                        new PersistableBundle());
-                return;
+            @Override
+            public void getFeature(int id, IFeatureCallback featureCallback)
+                    throws RemoteException {
+                Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+                Objects.requireNonNull(featureCallback);
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+                if (!mIsServiceEnabled) {
+                    Slog.w(TAG, "Service not available");
+                    featureCallback.onFailure(
+                            OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+                            "OnDeviceIntelligenceManagerService is unavailable",
+                            PersistableBundle.EMPTY);
+                    return;
+                }
+                ensureRemoteIntelligenceServiceInitialized();
+                mRemoteOnDeviceIntelligenceService.run(
+                        service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
             }
-            ensureRemoteIntelligenceServiceInitialized();
-            mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback));
-        }
 
-        @Override
-        public void getFeatureDetails(Feature feature,
-                IFeatureDetailsCallback featureDetailsCallback)
-                throws RemoteException {
-            Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
-            Objects.requireNonNull(feature);
-            Objects.requireNonNull(featureDetailsCallback);
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
-            if (!mIsServiceEnabled) {
-                Slog.w(TAG, "Service not available");
-                featureDetailsCallback.onFailure(
-                        OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
-                        "OnDeviceIntelligenceManagerService is unavailable",
-                        new PersistableBundle());
-                return;
+            @Override
+            public void listFeatures(IListFeaturesCallback listFeaturesCallback)
+                    throws RemoteException {
+                Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+                Objects.requireNonNull(listFeaturesCallback);
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+                if (!mIsServiceEnabled) {
+                    Slog.w(TAG, "Service not available");
+                    listFeaturesCallback.onFailure(
+                            OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+                            "OnDeviceIntelligenceManagerService is unavailable",
+                            PersistableBundle.EMPTY);
+                    return;
+                }
+                ensureRemoteIntelligenceServiceInitialized();
+                mRemoteOnDeviceIntelligenceService.run(
+                        service -> service.listFeatures(Binder.getCallingUid(),
+                                listFeaturesCallback));
             }
-            ensureRemoteIntelligenceServiceInitialized();
-            mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
-                            featureDetailsCallback));
-        }
 
-        @Override
-        public void requestFeatureDownload(Feature feature, ICancellationSignal cancellationSignal,
-                IDownloadCallback downloadCallback) throws RemoteException {
-            Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
-            Objects.requireNonNull(feature);
-            Objects.requireNonNull(downloadCallback);
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
-            if (!mIsServiceEnabled) {
-                Slog.w(TAG, "Service not available");
-                downloadCallback.onDownloadFailed(
-                        DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
-                        "OnDeviceIntelligenceManagerService is unavailable",
-                        new PersistableBundle());
+            @Override
+            public void getFeatureDetails(Feature feature,
+                    IFeatureDetailsCallback featureDetailsCallback)
+                    throws RemoteException {
+                Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
+                Objects.requireNonNull(feature);
+                Objects.requireNonNull(featureDetailsCallback);
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+                if (!mIsServiceEnabled) {
+                    Slog.w(TAG, "Service not available");
+                    featureDetailsCallback.onFailure(
+                            OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+                            "OnDeviceIntelligenceManagerService is unavailable",
+                            PersistableBundle.EMPTY);
+                    return;
+                }
+                ensureRemoteIntelligenceServiceInitialized();
+                mRemoteOnDeviceIntelligenceService.run(
+                        service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
+                                featureDetailsCallback));
             }
-            ensureRemoteIntelligenceServiceInitialized();
-            mRemoteOnDeviceIntelligenceService.post(
-                    service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
-                            cancellationSignal,
-                            downloadCallback));
-        }
 
-
-        @Override
-        public void requestTokenInfo(Feature feature,
-                Content request, ICancellationSignal cancellationSignal,
-                ITokenInfoCallback tokenInfoCallback) throws RemoteException {
-            Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
-            Objects.requireNonNull(feature);
-            Objects.requireNonNull(request);
-            Objects.requireNonNull(tokenInfoCallback);
-
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
-            if (!mIsServiceEnabled) {
-                Slog.w(TAG, "Service not available");
-                tokenInfoCallback.onFailure(
-                        OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
-                        "OnDeviceIntelligenceManagerService is unavailable",
-                        new PersistableBundle());
+            @Override
+            public void requestFeatureDownload(Feature feature,
+                    ICancellationSignal cancellationSignal,
+                    IDownloadCallback downloadCallback) throws RemoteException {
+                Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
+                Objects.requireNonNull(feature);
+                Objects.requireNonNull(downloadCallback);
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+                if (!mIsServiceEnabled) {
+                    Slog.w(TAG, "Service not available");
+                    downloadCallback.onDownloadFailed(
+                            DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
+                            "OnDeviceIntelligenceManagerService is unavailable",
+                            PersistableBundle.EMPTY);
+                }
+                ensureRemoteIntelligenceServiceInitialized();
+                mRemoteOnDeviceIntelligenceService.run(
+                        service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
+                                cancellationSignal,
+                                downloadCallback));
             }
-            ensureRemoteInferenceServiceInitialized();
-            mRemoteInferenceService.post(
-                    service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request,
-                            cancellationSignal,
-                            tokenInfoCallback));
-        }
 
-        @Override
-        public void processRequest(Feature feature,
-                Content request,
-                int requestType,
-                ICancellationSignal cancellationSignal,
-                IProcessingSignal processingSignal,
-                IResponseCallback responseCallback)
-                throws RemoteException {
-            Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
-            Objects.requireNonNull(feature);
-            Objects.requireNonNull(responseCallback);
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
-            if (!mIsServiceEnabled) {
-                Slog.w(TAG, "Service not available");
-                responseCallback.onFailure(
-                        OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
-                        "OnDeviceIntelligenceManagerService is unavailable",
-                        new PersistableBundle());
-            }
-            ensureRemoteInferenceServiceInitialized();
-            mRemoteInferenceService.post(
-                    service -> service.processRequest(Binder.getCallingUid(), feature, request,
-                            requestType,
-                            cancellationSignal, processingSignal,
-                            responseCallback));
-        }
 
-        @Override
-        public void processRequestStreaming(Feature feature,
-                Content request,
-                int requestType,
-                ICancellationSignal cancellationSignal,
-                IProcessingSignal processingSignal,
-                IStreamingResponseCallback streamingCallback) throws RemoteException {
-            Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
-            Objects.requireNonNull(feature);
-            Objects.requireNonNull(streamingCallback);
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
-            if (!mIsServiceEnabled) {
-                Slog.w(TAG, "Service not available");
-                streamingCallback.onFailure(
-                        OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
-                        "OnDeviceIntelligenceManagerService is unavailable",
-                        new PersistableBundle());
+            @Override
+            public void requestTokenInfo(Feature feature,
+                    Bundle request, ICancellationSignal cancellationSignal,
+                    ITokenInfoCallback tokenInfoCallback) throws RemoteException {
+                Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
+                Objects.requireNonNull(feature);
+                Objects.requireNonNull(request);
+                Objects.requireNonNull(tokenInfoCallback);
+
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+                if (!mIsServiceEnabled) {
+                    Slog.w(TAG, "Service not available");
+                    tokenInfoCallback.onFailure(
+                            OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+                            "OnDeviceIntelligenceManagerService is unavailable",
+                            PersistableBundle.EMPTY);
+                }
+                ensureRemoteInferenceServiceInitialized();
+                mRemoteInferenceService.run(
+                        service -> service.requestTokenInfo(Binder.getCallingUid(), feature,
+                                request,
+                                cancellationSignal,
+                                tokenInfoCallback));
             }
-            ensureRemoteInferenceServiceInitialized();
-            mRemoteInferenceService.post(
-                    service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
-                            request, requestType,
-                            cancellationSignal, processingSignal,
-                            streamingCallback));
-        }
+
+            @Override
+            public void processRequest(Feature feature,
+                    Bundle request,
+                    int requestType,
+                    ICancellationSignal cancellationSignal,
+                    IProcessingSignal processingSignal,
+                    IResponseCallback responseCallback)
+                    throws RemoteException {
+                Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
+                Objects.requireNonNull(feature);
+                Objects.requireNonNull(responseCallback);
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+                if (!mIsServiceEnabled) {
+                    Slog.w(TAG, "Service not available");
+                    responseCallback.onFailure(
+                            OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+                            "OnDeviceIntelligenceManagerService is unavailable",
+                            PersistableBundle.EMPTY);
+                }
+                ensureRemoteInferenceServiceInitialized();
+                mRemoteInferenceService.run(
+                        service -> service.processRequest(Binder.getCallingUid(), feature, request,
+                                requestType,
+                                cancellationSignal, processingSignal,
+                                responseCallback));
+            }
+
+            @Override
+            public void processRequestStreaming(Feature feature,
+                    Bundle request,
+                    int requestType,
+                    ICancellationSignal cancellationSignal,
+                    IProcessingSignal processingSignal,
+                    IStreamingResponseCallback streamingCallback) throws RemoteException {
+                Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
+                Objects.requireNonNull(feature);
+                Objects.requireNonNull(streamingCallback);
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+                if (!mIsServiceEnabled) {
+                    Slog.w(TAG, "Service not available");
+                    streamingCallback.onFailure(
+                            OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+                            "OnDeviceIntelligenceManagerService is unavailable",
+                            PersistableBundle.EMPTY);
+                }
+                ensureRemoteInferenceServiceInitialized();
+                mRemoteInferenceService.run(
+                        service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
+                                request, requestType,
+                                cancellationSignal, processingSignal,
+                                streamingCallback));
+            }
+
+            @Override
+            public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                    String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+                new OnDeviceIntelligenceShellCommand(OnDeviceIntelligenceManagerService.this).exec(
+                        this, in, out, err, args, callback, resultReceiver);
+            }
+        };
     }
 
     private void ensureRemoteIntelligenceServiceInitialized() throws RemoteException {
         synchronized (mLock) {
             if (mRemoteOnDeviceIntelligenceService == null) {
-                String serviceName = mContext.getResources().getString(
-                        R.string.config_defaultOnDeviceIntelligenceService);
+                String serviceName = getServiceNames()[0];
                 validateService(serviceName, false);
                 mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
                         ComponentName.unflattenFromString(serviceName),
@@ -352,13 +390,13 @@
                     IProcessingUpdateStatusCallback callback) {
                 try {
                     ensureRemoteInferenceServiceInitialized();
-                    mRemoteInferenceService.post(
+                    mRemoteInferenceService.run(
                             service -> service.updateProcessingState(
                                     processingState, callback));
                 } catch (RemoteException unused) {
                     try {
                         callback.onFailure(
-                                PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
+                                OnDeviceIntelligenceException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
                                 "Received failure invoking the remote processing service.");
                     } catch (RemoteException ex) {
                         Slog.w(TAG, "Failed to send failure status.", ex);
@@ -371,8 +409,7 @@
     private void ensureRemoteInferenceServiceInitialized() throws RemoteException {
         synchronized (mLock) {
             if (mRemoteInferenceService == null) {
-                String serviceName = mContext.getResources().getString(
-                        R.string.config_defaultOnDeviceSandboxedInferenceService);
+                String serviceName = getServiceNames()[1];
                 validateService(serviceName, true);
                 mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
                         ComponentName.unflattenFromString(serviceName),
@@ -384,7 +421,7 @@
                                     @NonNull IOnDeviceSandboxedInferenceService service) {
                                 try {
                                     ensureRemoteIntelligenceServiceInitialized();
-                                    mRemoteOnDeviceIntelligenceService.post(
+                                    mRemoteOnDeviceIntelligenceService.run(
                                             intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
                                     service.registerRemoteStorageService(
                                             getIRemoteStorageService());
@@ -404,7 +441,7 @@
             public void getReadOnlyFileDescriptor(
                     String filePath,
                     AndroidFuture<ParcelFileDescriptor> future) {
-                mRemoteOnDeviceIntelligenceService.post(
+                mRemoteOnDeviceIntelligenceService.run(
                         service -> service.getReadOnlyFileDescriptor(
                                 filePath, future));
             }
@@ -413,7 +450,7 @@
             public void getReadOnlyFeatureFileDescriptorMap(
                     Feature feature,
                     RemoteCallback remoteCallback) {
-                mRemoteOnDeviceIntelligenceService.post(
+                mRemoteOnDeviceIntelligenceService.run(
                         service -> service.getReadOnlyFeatureFileDescriptorMap(
                                 feature, remoteCallback));
             }
@@ -469,4 +506,92 @@
         return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
                 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
     }
+
+    @Nullable
+    public String getRemoteConfiguredPackageName() {
+        try {
+            String[] serviceNames = getServiceNames();
+            ComponentName componentName = ComponentName.unflattenFromString(serviceNames[1]);
+            if (componentName != null) {
+                return componentName.getPackageName();
+            }
+        } catch (Resources.NotFoundException e) {
+            Slog.e(TAG, "Could not find resource", e);
+        }
+
+        return null;
+    }
+
+
+    protected String[] getServiceNames() throws Resources.NotFoundException {
+        // TODO 329240495 : Consider a small class with explicit field names for the two services
+        synchronized (mLock) {
+            if (mTemporaryServiceNames != null && mTemporaryServiceNames.length == 2) {
+                return mTemporaryServiceNames;
+            }
+        }
+        return new String[]{mContext.getResources().getString(
+                R.string.config_defaultOnDeviceIntelligenceService),
+                mContext.getResources().getString(
+                        R.string.config_defaultOnDeviceSandboxedInferenceService)};
+    }
+
+    @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+    public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) {
+        Objects.requireNonNull(componentNames);
+        enforceShellOnly(Binder.getCallingUid(), "setTemporaryServices");
+        mContext.enforceCallingPermission(
+                Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+        synchronized (mLock) {
+            mTemporaryServiceNames = componentNames;
+
+            if (mTemporaryHandler == null) {
+                mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+                            synchronized (mLock) {
+                                resetTemporaryServices();
+                            }
+                        } else {
+                            Slog.wtf(TAG, "invalid handler msg: " + msg);
+                        }
+                    }
+                };
+            } else {
+                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+            }
+
+            if (durationMs != -1) {
+                mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+            }
+        }
+    }
+
+    public void resetTemporaryServices() {
+        synchronized (mLock) {
+            if (mTemporaryHandler != null) {
+                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+                mTemporaryHandler = null;
+            }
+
+            mRemoteInferenceService = null;
+            mRemoteOnDeviceIntelligenceService = null;
+            mTemporaryServiceNames = new String[0];
+        }
+    }
+
+    /**
+     * Throws if the caller is not of a shell (or root) UID.
+     *
+     * @param callingUid pass Binder.callingUid().
+     */
+    public static void enforceShellOnly(int callingUid, String message) {
+        if (callingUid == android.os.Process.SHELL_UID
+                || callingUid == android.os.Process.ROOT_UID) {
+            return; // okay
+        }
+
+        throw new SecurityException(message + ": Only shell user can call it");
+    }
 }
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
new file mode 100644
index 0000000..a76d8a3
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 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.ondeviceintelligence;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+final class OnDeviceIntelligenceShellCommand extends ShellCommand {
+    private static final String TAG = OnDeviceIntelligenceShellCommand.class.getSimpleName();
+
+    @NonNull
+    private final OnDeviceIntelligenceManagerService mService;
+
+    OnDeviceIntelligenceShellCommand(@NonNull OnDeviceIntelligenceManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+
+        switch (cmd) {
+            case "set-temporary-services":
+                return setTemporaryServices();
+            case "get-services":
+                return getConfiguredServices();
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("OnDeviceIntelligenceShellCommand commands: ");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println(
+                "  set-temporary-services [IntelligenceServiceComponentName] "
+                        + "[InferenceServiceComponentName] [DURATION]");
+        pw.println("    Temporarily (for DURATION ms) changes the service implementations.");
+        pw.println("    To reset, call without any arguments.");
+
+        pw.println("  get-services To get the names of services that are currently being used.");
+    }
+
+    private int setTemporaryServices() {
+        final PrintWriter out = getOutPrintWriter();
+        final String intelligenceServiceName = getNextArg();
+        final String inferenceServiceName = getNextArg();
+        if (getRemainingArgsCount() == 0 && intelligenceServiceName == null
+                && inferenceServiceName == null) {
+            mService.resetTemporaryServices();
+            out.println("OnDeviceIntelligenceManagerService temporary reset. ");
+            return 0;
+        }
+
+        Objects.requireNonNull(intelligenceServiceName);
+        Objects.requireNonNull(inferenceServiceName);
+        final int duration = Integer.parseInt(getNextArgRequired());
+        mService.setTemporaryServices(
+                new String[]{intelligenceServiceName, inferenceServiceName}, duration);
+        out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName
+                + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName
+                + " for " + duration + "ms");
+        return 0;
+    }
+
+    private int getConfiguredServices() {
+        final PrintWriter out = getOutPrintWriter();
+        String[] services = mService.getServiceNames();
+        out.println("OnDeviceIntelligenceService set to :  " + services[0]
+                + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]);
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4bfd077..4bec61a 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2892,17 +2892,20 @@
                 mPm.notifyPackageChanged(packageName, request.getAppId());
             }
 
-            // Apply restricted settings on potentially dangerous packages. Needs to happen
-            // after appOpsManager is notified of the new package
-            if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
-                    || request.getPackageSource()
-                    == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
-                final int appId = request.getAppId();
-                mPm.mHandler.post(() -> {
-                    for (int userId : firstUserIds) {
-                        enableRestrictedSettings(packageName, appId, userId);
-                    }
-                });
+            if (!android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+                    || !android.security.Flags.extendEcmToAllSettings()) {
+                // Apply restricted settings on potentially dangerous packages. Needs to happen
+                // after appOpsManager is notified of the new package
+                if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+                        || request.getPackageSource()
+                        == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
+                    final int appId = request.getAppId();
+                    mPm.mHandler.post(() -> {
+                        for (int userId : firstUserIds) {
+                            enableRestrictedSettings(packageName, appId, userId);
+                        }
+                    });
+                }
             }
 
             // Log current value of "unknown sources" setting
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ba90378..9e4ea6a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -964,13 +964,22 @@
 
     private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
         final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
-        if (ps == null || ps.getPkg() == null) {
+        if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
             return false;
         }
+        int uid = UserHandle.getUid(userId, ps.getAppId());
         String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
         if (emergencyInstaller == null || !ArrayUtils.contains(
-                snapshot.getPackagesForUid(mInstallerUid),
-                emergencyInstaller)) {
+                snapshot.getPackagesForUid(mInstallerUid), emergencyInstaller)) {
+            return false;
+        }
+        // Only system installers can have an emergency installer
+        if (PackageManager.PERMISSION_GRANTED
+                != snapshot.checkUidPermission(Manifest.permission.INSTALL_PACKAGES, uid)
+                && PackageManager.PERMISSION_GRANTED
+                != snapshot.checkUidPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES, uid)
+                && PackageManager.PERMISSION_GRANTED
+                != snapshot.checkUidPermission(Manifest.permission.INSTALL_SELF_UPDATES, uid)) {
             return false;
         }
         return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES,
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 0165d65..65ab129 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -314,6 +314,11 @@
             wallpaper.wallpaperId = makeWallpaperIdLocked();
         }
 
+        Rect legacyCropHint = new Rect(
+                getAttributeInt(parser, "cropLeft", 0),
+                getAttributeInt(parser, "cropTop", 0),
+                getAttributeInt(parser, "cropRight", 0),
+                getAttributeInt(parser, "cropBottom", 0));
         Rect totalCropHint = new Rect(
                 getAttributeInt(parser, "totalCropLeft", 0),
                 getAttributeInt(parser, "totalCropTop", 0),
@@ -332,18 +337,19 @@
                         parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
                 if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
             }
-            if (wallpaper.mCropHints.size() == 0) {
+            if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
                 // migration case: the crops per screen orientation are not specified.
-                // use the old attributes to find the crop for one screen orientation.
-                Integer orientation = totalCropHint.width() < totalCropHint.height()
+                int orientation = legacyCropHint.width() < legacyCropHint.height()
                         ? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
-                if (!totalCropHint.isEmpty()) wallpaper.mCropHints.put(orientation, totalCropHint);
+                if (!legacyCropHint.isEmpty()) {
+                    wallpaper.mCropHints.put(orientation, legacyCropHint);
+                }
             } else {
                 wallpaper.cropHint.set(totalCropHint);
             }
             wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
         } else {
-            wallpaper.cropHint.set(totalCropHint);
+            wallpaper.cropHint.set(legacyCropHint);
         }
         final DisplayData wpData = mWallpaperDisplayHelper
                 .getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0069cdd..23f9743 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -118,6 +118,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
+import static android.view.WindowManager.ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -125,10 +126,12 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
 import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
+import static android.view.WindowManager.ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.hasWindowExtensionsEnabled;
 import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -282,6 +285,7 @@
 import android.app.WindowConfiguration;
 import android.app.admin.DevicePolicyManager;
 import android.app.assist.ActivityId;
+import android.app.compat.CompatChanges;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -2266,16 +2270,7 @@
 
         mActivityRecordInputSink = new ActivityRecordInputSink(this, sourceRecord);
 
-        boolean appActivityEmbeddingEnabled = false;
-        try {
-            appActivityEmbeddingEnabled = WindowManager.hasWindowExtensionsEnabled()
-                    && mAtmService.mContext.getPackageManager()
-                            .getProperty(PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED, packageName)
-                            .getBoolean();
-        } catch (PackageManager.NameNotFoundException e) {
-            // No such property name.
-        }
-        mAppActivityEmbeddingSplitsEnabled = appActivityEmbeddingEnabled;
+        mAppActivityEmbeddingSplitsEnabled = isAppActivityEmbeddingSplitsEnabled();
         mAllowUntrustedEmbeddingStateSharing = getAllowUntrustedEmbeddingStateSharingProperty();
 
         mOptInOnBackInvoked = WindowOnBackInvokedDispatcher
@@ -2294,6 +2289,28 @@
         mCallerState = new ActivityCallerState(mAtmService);
     }
 
+    private boolean isAppActivityEmbeddingSplitsEnabled() {
+        if (!hasWindowExtensionsEnabled()) {
+            // WM Extensions disabled.
+            return false;
+        }
+        if (ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15 && !CompatChanges.isChangeEnabled(
+                ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15,
+                info.packageName,
+                UserHandle.getUserHandleForUid(getUid()))) {
+            // Activity Embedding is guarded with Android 15+, but this app is not qualified.
+            return false;
+        }
+        try {
+            return mAtmService.mContext.getPackageManager()
+                    .getProperty(PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED, packageName)
+                    .getBoolean();
+        } catch (PackageManager.NameNotFoundException e) {
+            // No such property name.
+            return false;
+        }
+    }
+
     /**
      * Generate the task affinity with uid and activity launch mode. For b/35954083, Limit task
      * affinity to uid to avoid issues associated with sharing affinity across uids.
@@ -7786,8 +7803,11 @@
 
     @Override
     void prepareSurfaces() {
-        final boolean show = isVisible() || isAnimating(PARENTS,
-                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+        final boolean show = (isVisible()
+                // Ensure that the activity content is hidden when the decor surface is boosted to
+                // prevent UI redressing attack.
+                && !getTask().isDecorSurfaceBoosted())
+                || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
                         | ANIMATION_TYPE_PREDICT_BACK);
 
         if (mSurfaceControl != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 2fc6b5f..963b4cb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1478,7 +1478,6 @@
                 }
                 mLaunchingActivityWakeLock.release();
             }
-            mRootWindowContainer.ensureActivitiesVisible();
         }
 
         // Atomically retrieve all of the other things to do.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d87e21c..55dc30c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3741,7 +3741,9 @@
             wc.assignChildLayers(t);
             if (!wc.needsZBoost()) {
                 // Place the decor surface under any untrusted content.
-                if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+                if (mDecorSurfaceContainer != null
+                        && !mDecorSurfaceContainer.mIsBoosted
+                        && !decorSurfacePlaced
                         && shouldPlaceDecorSurfaceBelowContainer(wc)) {
                     mDecorSurfaceContainer.assignLayer(t, layer++);
                     decorSurfacePlaced = true;
@@ -3760,7 +3762,9 @@
                 }
 
                 // Place the decor surface just above the owner TaskFragment.
-                if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+                if (mDecorSurfaceContainer != null
+                        && !mDecorSurfaceContainer.mIsBoosted
+                        && !decorSurfacePlaced
                         && wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
                     mDecorSurfaceContainer.assignLayer(t, layer++);
                     decorSurfacePlaced = true;
@@ -3768,10 +3772,10 @@
             }
         }
 
-        // If not placed yet, the decor surface should be on top of all non-boosted children.
-        if (mDecorSurfaceContainer != null && !decorSurfacePlaced) {
+        // Boost the decor surface above other non-boosted windows if requested. The cover surface
+        // will ensure that the content of the windows below are invisible.
+        if (mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted) {
             mDecorSurfaceContainer.assignLayer(t, layer++);
-            decorSurfacePlaced = true;
         }
 
         for (int j = 0; j < mChildren.size(); ++j) {
@@ -3796,6 +3800,24 @@
         return !isOwnActivity && !isTrustedTaskFragment;
     }
 
+    void setDecorSurfaceBoosted(
+            @NonNull TaskFragment ownerTaskFragment,
+            boolean isBoosted,
+            @Nullable SurfaceControl.Transaction clientTransaction) {
+        if (mDecorSurfaceContainer == null
+                || mDecorSurfaceContainer.mOwnerTaskFragment != ownerTaskFragment) {
+            return;
+        }
+        mDecorSurfaceContainer.setBoosted(isBoosted, clientTransaction);
+        // scheduleAnimation() is called inside assignChildLayers(), which ensures that child
+        // surface visibility is updated with prepareSurfaces()
+        assignChildLayers();
+    }
+
+    boolean isDecorSurfaceBoosted() {
+        return mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted;
+    }
+
     boolean isTaskId(int taskId) {
         return mTaskId == taskId;
     }
@@ -6796,14 +6818,35 @@
     }
 
     /**
-     * A decor surface that is requested by a {@code TaskFragmentOrganizer} which will be placed
-     * below children windows except for own Activities and TaskFragment in fully trusted mode.
+     * A class managing the decor surface.
+     *
+     * A decor surface is requested by a {@link TaskFragmentOrganizer} and is placed below children
+     * windows in the Task except for own Activities and TaskFragments in fully trusted mode. The
+     * decor surface is created and shared with the client app with
+     * {@link android.window.TaskFragmentOperation#OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE} and
+     * be removed with
+     * {@link android.window.TaskFragmentOperation#OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE}.
+     *
+     * When boosted with
+     * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED}, the decor
+     * surface is placed above all non-boosted windows in the Task, but all the content below it
+     * will be hidden to prevent UI redressing attacks. This can be used by the draggable
+     * divider between {@link TaskFragment}s where veils are drawn on the decor surface while
+     * dragging to indicate new bounds.
      */
     @VisibleForTesting
     class DecorSurfaceContainer {
+
+        // The container surface is the parent of the decor surface. The container surface
+        // should NEVER be shared with the client. It is used to ensure that the decor surface has
+        // a z-order in the Task that is managed by WM core and cannot be updated by the client
+        // process.
         @VisibleForTesting
         @NonNull final SurfaceControl mContainerSurface;
 
+        // The decor surface is shared with the client process owning the
+        // {@link TaskFragmentOrganizer}. It can be used to draw the divider between TaskFragments
+        // or other decorations.
         @VisibleForTesting
         @NonNull final SurfaceControl mDecorSurface;
 
@@ -6812,12 +6855,18 @@
         @VisibleForTesting
         @NonNull TaskFragment mOwnerTaskFragment;
 
+        private boolean mIsBoosted;
+
+        // The surface transactions that will be applied when the layer is reassigned.
+        @NonNull private final List<SurfaceControl.Transaction> mPendingClientTransactions =
+                new ArrayList<>();
+
         private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) {
             mOwnerTaskFragment = initialOwner;
             mContainerSurface = makeSurface().setContainerLayer()
                     .setParent(mSurfaceControl)
                     .setName(mSurfaceControl + " - decor surface container")
-                    .setEffectLayer()
+                    .setContainerLayer()
                     .setHidden(false)
                     .setCallsite("Task.DecorSurfaceContainer")
                     .build();
@@ -6830,14 +6879,28 @@
                     .build();
         }
 
+        private void setBoosted(
+                boolean isBoosted, @Nullable SurfaceControl.Transaction clientTransaction) {
+            mIsBoosted = isBoosted;
+            // The client transaction will be applied together with the next assignLayer.
+            if (clientTransaction != null) {
+                mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction);
+            }
+        }
+
         private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) {
             t.setLayer(mContainerSurface, layer);
             t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible());
+            for (int i = 0; i < mPendingClientTransactions.size(); i++) {
+                t.merge(mPendingClientTransactions.get(i));
+            }
+            mPendingClientTransactions.clear();
         }
 
         private void release() {
-            mDecorSurface.release();
-            mContainerSurface.release();
+            getSyncTransaction()
+                    .remove(mDecorSurface)
+                    .remove(mContainerSurface);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c919250..3cf561c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -996,8 +996,7 @@
             } else {
                 // Still have something resumed; can't sleep until it is paused.
                 ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
-                startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
-                        "sleep");
+                startPausing(true /* uiSleeping */, null /* resuming */, "sleep");
             }
             shouldSleep = false;
         } else if (mPausingActivity != null) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index a84a99a..74dad91 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -671,12 +671,10 @@
             if (hasImeSurface) {
                 if (topActivity.isVisibleRequested() && dc.mInputMethodWindow != null
                         && dc.isFixedRotationLaunchingApp(topActivity)) {
-                    removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION;
+                    removalInfo.deferRemoveMode = DEFER_MODE_ROTATION;
                 } else {
-                    removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL;
+                    removalInfo.deferRemoveMode = DEFER_MODE_NORMAL;
                 }
-            } else {
-                removalInfo.deferRemoveForImeMode = DEFER_MODE_NONE;
             }
 
             final WindowState mainWindow =
@@ -745,6 +743,7 @@
         removalInfo.taskId = taskId;
         removalInfo.windowlessSurface = true;
         removalInfo.removeImmediately = immediately;
+        removalInfo.deferRemoveMode = DEFER_MODE_NONE;
         try {
             lastOrganizer.removeStartingWindow(removalInfo);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 71ffabf..40b1b20 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9098,7 +9098,7 @@
         Objects.requireNonNull(outInputChannel);
         synchronized (mGlobalLock) {
             WindowState hostWindowState = hostInputTransferToken != null
-                    ? mInputToWindowMap.get(hostInputTransferToken.mToken) : null;
+                    ? mInputToWindowMap.get(hostInputTransferToken.getToken()) : null;
             EmbeddedWindowController.EmbeddedWindow win =
                     new EmbeddedWindowController.EmbeddedWindow(session, this, clientToken,
                             hostWindowState, callingUid, callingPid, sanitizedType, displayId,
@@ -9127,12 +9127,13 @@
                 // If the transferToToken exists in the input to window map, it means the request
                 // is to transfer from embedded to host. Otherwise, the transferToToken
                 // represents an embedded window so transfer from host to embedded.
-                WindowState windowStateTo = mInputToWindowMap.get(transferToToken.mToken);
+                WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken());
                 if (windowStateTo != null) {
                     didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken,
                             windowStateTo);
                 } else {
-                    WindowState windowStateFrom = mInputToWindowMap.get(transferFromToken.mToken);
+                    WindowState windowStateFrom = mInputToWindowMap.get(
+                            transferFromToken.getToken());
                     didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom,
                             transferToToken);
                 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index a6db310f..731184f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -519,6 +519,9 @@
             case "default":
                 fixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
                 break;
+            case "enabled_if_no_auto_rotation":
+                fixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION;
+                break;
             default:
                 getErrPrintWriter().println("Error: expecting enabled, disabled or default, but we "
                         + "get " + arg);
@@ -538,6 +541,9 @@
             case IWindowManager.FIXED_TO_USER_ROTATION_DISABLED:
                 pw.println("disabled");
                 return 0;
+            case IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION:
+                pw.println("enabled_if_no_auto_rotation");
+                return 0;
             case IWindowManager.FIXED_TO_USER_ROTATION_ENABLED:
                 pw.println("enabled");
                 return 0;
@@ -1494,7 +1500,8 @@
         pw.println("    Print or set user rotation mode and user rotation.");
         pw.println("  dump-visible-window-views");
         pw.println("    Dumps the encoded view hierarchies of visible windows");
-        pw.println("  fixed-to-user-rotation [-d DISPLAY_ID] [enabled|disabled|default]");
+        pw.println("  fixed-to-user-rotation [-d DISPLAY_ID] [enabled|disabled|default");
+        pw.println("      |enabled_if_no_auto_rotation]");
         pw.println("    Print or set rotating display for app requested orientation.");
         pw.println("  set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]");
         pw.println("  get-ignore-orientation-request [-d DISPLAY_ID] ");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a63e106..d967cde 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -34,6 +34,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
@@ -124,6 +125,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
+import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -1557,13 +1559,11 @@
                 break;
             }
             case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: {
-                final Task task = taskFragment.getTask();
-                task.moveOrCreateDecorSurfaceFor(taskFragment);
+                taskFragment.getTask().moveOrCreateDecorSurfaceFor(taskFragment);
                 break;
             }
             case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: {
-                final Task task = taskFragment.getTask();
-                task.removeDecorSurface();
+                taskFragment.getTask().removeDecorSurface();
                 break;
             }
             case OP_TYPE_SET_DIM_ON_TASK: {
@@ -1577,6 +1577,23 @@
                         operation.isMoveToBottomIfClearWhenLaunch());
                 break;
             }
+            case OP_TYPE_SET_DECOR_SURFACE_BOOSTED: {
+                if (Flags.activityEmbeddingInteractiveDividerFlag()) {
+                    final SurfaceControl.Transaction clientTransaction =
+                            operation.getSurfaceTransaction();
+                    if (clientTransaction != null) {
+                        // Sanitize the client transaction. sanitize() silently removes invalid
+                        // operations and does not throw or provide signal about whether there are
+                        // any invalid operations.
+                        clientTransaction.sanitize(caller.mPid, caller.mUid);
+                    }
+                    taskFragment.getTask().setDecorSurfaceBoosted(
+                            taskFragment,
+                            operation.getBooleanValue() /* isBoosted */,
+                            clientTransaction);
+                }
+                break;
+            }
         }
         return effects;
     }
@@ -1616,19 +1633,6 @@
             return false;
         }
 
-        // TODO (b/293654166) remove the decor surface checks once we clear security reviews
-        if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE
-                || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
-                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
-            final Throwable exception = new SecurityException(
-                    "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE"
-                            + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE."
-            );
-            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
-                    opType, exception);
-            return false;
-        }
-
         if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
                 && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
             final Throwable exception = new SecurityException(
@@ -1766,6 +1770,13 @@
                 throw new RuntimeException("Reparenting leaf Tasks is not supported now. " + task);
             }
         } else {
+            if (hop.getToTop() && task.isRootTask()) {
+                final ActivityRecord pipCandidate = task.findEnterPipOnTaskSwitchCandidate(
+                        task.getDisplayArea().getTopRootTask());
+                task.enableEnterPipOnTaskSwitch(pipCandidate, task, null /* toFrontActivity */,
+                        null /* options */);
+            }
+
             task.getParent().positionChildAt(
                     hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
                     task, false /* includingParents */);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c778398..610fcb5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -502,7 +502,7 @@
                 toString(mLocked.pointerGesturesEnabled));
         dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
         dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
-                             mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+                             mLocked.pointerCaptureRequest.isEnable() ? "Enabled" : "Disabled",
                              mLocked.pointerCaptureRequest.seq);
         if (auto pc = mLocked.legacyPointerController.lock(); pc) {
             dump += pc->dump();
@@ -1717,7 +1717,7 @@
             return;
         }
 
-        ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+        ALOGV("%s pointer capture.", request.isEnable() ? "Enabling" : "Disabling");
         mLocked.pointerCaptureRequest = request;
     } // release lock
 
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 96ef2ed..bdea4f9 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -568,7 +568,10 @@
                     /* clicked_entries */ browsedClickedEntries,
                     /* provider_of_clicked_entry */ browsedProviderUid,
                     /* api_status */ apiStatus,
-                    /* primary_indicated */ finalPhaseMetric.isPrimary()
+                    /* primary_indicated */ finalPhaseMetric.isPrimary(),
+                    /* oem_credential_manager_ui_uid */ finalPhaseMetric.getOemUiUid(),
+                    /* fallback_credential_manager_ui_uid */ finalPhaseMetric.getFallbackUiUid(),
+                    /* oem_ui_usage_status */ finalPhaseMetric.getOemUiUsageStatus()
             );
         } catch (Exception e) {
             Slog.w(TAG, "Unexpected error during final no uid metric logging: " + e);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index cad9a09..c5f2921 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -182,7 +182,7 @@
             CredentialProviderInfo info,
             String hybridService) {
         Slog.i(TAG, "Filtering request options for: " + info.getComponentName());
-        if (android.credentials.flags.Flags.hybridFilterFixEnabled()) {
+        if (android.credentials.flags.Flags.hybridFilterOptFixEnabled()) {
             ComponentName hybridComponentName = ComponentName.unflattenFromString(hybridService);
             if (hybridComponentName != null && hybridComponentName
                     .equals(info.getComponentName())) {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index 468d3c8..9dd6db6 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -67,6 +67,10 @@
 
     // Other General Information, such as final api status, provider status, entry info, etc...
 
+    private int mOemUiUid = -1;
+    private int mFallbackUiUid = -1;
+    private OemUiUsageStatus mOemUiUsageStatus = OemUiUsageStatus.UNKNOWN;
+
     private int mChosenProviderStatus = -1;
     // Indicates if an exception was thrown by this provider, false by default
     private boolean mHasException = false;
@@ -302,4 +306,28 @@
     public boolean isPrimary() {
         return mIsPrimary;
     }
+
+    public void setOemUiUid(int oemUiUid) {
+        mOemUiUid = oemUiUid;
+    }
+
+    public int getOemUiUid() {
+        return mOemUiUid;
+    }
+
+    public void setFallbackUiUid(int fallbackUiUid) {
+        mFallbackUiUid = fallbackUiUid;
+    }
+
+    public int getFallbackUiUid() {
+        return mFallbackUiUid;
+    }
+
+    public void setOemUiUsageStatus(OemUiUsageStatus oemUiUsageStatus) {
+        mOemUiUsageStatus = oemUiUsageStatus;
+    }
+
+    public int getOemUiUsageStatus() {
+        return mOemUiUsageStatus.getLoggingInt();
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
new file mode 100644
index 0000000..2fd3a86
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
@@ -0,0 +1,42 @@
+/*
+ * 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.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_NOT_SPECIFIED;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED;
+
+
+public enum OemUiUsageStatus {
+    UNKNOWN(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN),
+    SUCCESS(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS),
+    FAILURE_NOT_SPECIFIED(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_NOT_SPECIFIED),
+    FAILURE_SPECIFIED_BUT_NOT_FOUND(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND),
+    FAILURE_SPECIFIED_BUT_NOT_ENABLED(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED);
+
+    private final int mLoggingInt;
+
+    OemUiUsageStatus(int loggingInt) {
+        mLoggingInt = loggingInt;
+    }
+
+    public int getLoggingInt() {
+        return mLoggingInt;
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ee72db0..38ab765 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3397,7 +3397,6 @@
                     }
                     maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked();
                     migratePoliciesToPolicyEngineLocked();
-
                 }
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
                 break;
@@ -12303,9 +12302,6 @@
         }
         final CallerIdentity caller = getCallerIdentity(admin);
 
-        // Only allow the system user to use this method
-        Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
-                "createAndManageUser was called from non-system user");
         Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
 
@@ -12316,6 +12312,10 @@
                     "createAndManageUser was called while in headless single user mode");
         }
 
+        // Only allow the system user to use this method
+        Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
+                "createAndManageUser was called from non-system user");
+
         final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
         final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0
                 && UserManager.isDeviceInDemoMode(mContext);
@@ -13179,27 +13179,47 @@
             CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled,
             boolean parent) {
         synchronized (getLockObject()) {
+
+            int ownerType;
             if (isDeviceOwner(caller)) {
-                if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) {
-                    setGlobalUserRestrictionInternal(admin, key, enabled);
-                } else {
-                    setLocalUserRestrictionInternal(admin, key, enabled, caller.getUserId());
-                }
+                ownerType = OWNER_TYPE_DEVICE_OWNER;
+            } else if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+                ownerType = OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE;
             } else if (isProfileOwner(caller)) {
-                if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key)
-                        || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)
-                        && UserRestrictionsUtils.isGlobal(
-                                OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) {
-                    setGlobalUserRestrictionInternal(admin, key, enabled);
-                } else {
-                    int affectedUserId = parent
-                            ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-                    setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
-                }
+                ownerType = OWNER_TYPE_PROFILE_OWNER;
             } else {
                 throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key
                         + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE");
             }
+            setBackwardCompatibleUserRestrictionLocked(ownerType, admin, caller.getUserId(), key,
+                    enabled, parent);
+        }
+    }
+
+    private void setBackwardCompatibleUserRestrictionLocked(
+            int ownerType, EnforcingAdmin admin, int userId, String key, boolean enabled,
+            boolean parent) {
+        if (ownerType == OWNER_TYPE_DEVICE_OWNER) {
+            if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) {
+                setGlobalUserRestrictionInternal(admin, key, enabled);
+            } else {
+                setLocalUserRestrictionInternal(admin, key, enabled, userId);
+            }
+        } else if (ownerType == OWNER_TYPE_PROFILE_OWNER
+                || ownerType == OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE) {
+            if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key)
+                    || (parent && ownerType == OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE
+                    && UserRestrictionsUtils.isGlobal(
+                    OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) {
+                setGlobalUserRestrictionInternal(admin, key, enabled);
+            } else {
+                int affectedUserId = parent
+                        ? getProfileParentId(userId) : userId;
+                setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
+            }
+        } else {
+            throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key
+                    + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE");
         }
     }
 
@@ -23747,13 +23767,15 @@
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
         return mInjector.binderWithCleanCallingIdentity(() -> {
-            boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins();
-            if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
-                return false;
+            synchronized (getLockObject()) {
+                boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins();
+                if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
+                    return false;
+                }
+                boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
+                migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+                return migrated;
             }
-            boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
-            migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
-            return migrated;
         });
     }
 
@@ -23797,6 +23819,7 @@
         try {
             migrateScreenCapturePolicyLocked();
             migrateLockTaskPolicyLocked();
+            migrateUserRestrictionsLocked();
             return true;
         } catch (Exception e) {
             Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device "
@@ -24066,6 +24089,42 @@
         });
     }
 
+    private void migrateUserRestrictionsLocked() {
+        Binder.withCleanCallingIdentity(() -> {
+            List<UserInfo> users = mUserManager.getUsers();
+            for (UserInfo userInfo : users) {
+                ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
+                if (admin == null) continue;
+                ComponentName adminComponent = admin.info.getComponent();
+                int userId = userInfo.id;
+                EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                        adminComponent,
+                        userId,
+                        admin);
+                int ownerType;
+                if (isDeviceOwner(admin)) {
+                    ownerType = OWNER_TYPE_DEVICE_OWNER;
+                } else if (isProfileOwnerOfOrganizationOwnedDevice(adminComponent, userId)) {
+                    ownerType = OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE;
+                } else if (isProfileOwner(adminComponent, userId)) {
+                    ownerType = OWNER_TYPE_PROFILE_OWNER;
+                } else {
+                    throw new IllegalStateException("Invalid DO/PO state");
+                }
+
+                for (final String restriction : admin.ensureUserRestrictions().keySet()) {
+                    setBackwardCompatibleUserRestrictionLocked(ownerType, enforcingAdmin, userId,
+                            restriction, /* enabled */ true, /* parent */ false);
+                }
+                for (final String restriction : admin.getParentActiveAdmin()
+                        .ensureUserRestrictions().keySet()) {
+                    setBackwardCompatibleUserRestrictionLocked(ownerType, enforcingAdmin, userId,
+                            restriction, /* enabled */ true, /* parent */ true);
+                }
+            }
+        });
+    }
+
     private List<PackageInfo> getInstalledPackagesOnUser(int userId) {
         return mInjector.binderWithCleanCallingIdentity(() ->
                 mContext.getPackageManager().getInstalledPackagesAsUser(
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 73d830d..c6189ed 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -161,6 +161,7 @@
 import com.android.server.notification.NotificationManagerService;
 import com.android.server.oemlock.OemLockService;
 import com.android.server.om.OverlayManagerService;
+import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService;
 import com.android.server.os.BugreportManagerService;
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.NativeTombstoneManagerService;
@@ -1965,6 +1966,7 @@
             startSystemCaptionsManagerService(context, t);
             startTextToSpeechManagerService(context, t);
             startWearableSensingService(t);
+            startOnDeviceIntelligenceService(t);
 
             if (deviceHasConfigString(
                     context, R.string.config_defaultAmbientContextDetectionService)) {
@@ -3335,6 +3337,12 @@
         t.traceEnd(); // startOtherServices
     }
 
+    private void startOnDeviceIntelligenceService(TimingsTraceAndSlog t) {
+        t.traceBegin("startOnDeviceIntelligenceManagerService");
+        mSystemServiceManager.startService(OnDeviceIntelligenceManagerService.class);
+        t.traceEnd();
+    }
+
     /**
      * Starts system services defined in apexes.
      *
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index af8ce31..7618740 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -268,14 +268,33 @@
         } else {
             newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED
         }
-        newFlags =
-            if (
-                permission.isSoftRestricted && !isExempt &&
-                    !anyPackageInAppId(appId) {
-                        permissionName in it.androidPackage!!.requestedPermissions &&
-                            isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+        val isSoftRestricted =
+            if (permission.isSoftRestricted && !isExempt) {
+                val targetSdkVersion =
+                    reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT) {
+                        targetSdkVersion,
+                        packageState ->
+                        if (permissionName in packageState.androidPackage!!.requestedPermissions) {
+                            targetSdkVersion.coerceAtMost(
+                                packageState.androidPackage!!.targetSdkVersion
+                            )
+                        } else {
+                            targetSdkVersion
+                        }
                     }
-            ) {
+                !anyPackageInAppId(appId) {
+                    permissionName in it.androidPackage!!.requestedPermissions &&
+                        isSoftRestrictedPermissionExemptForPackage(
+                            it,
+                            targetSdkVersion,
+                            permissionName
+                        )
+                }
+            } else {
+                false
+            }
+        newFlags =
+            if (isSoftRestricted) {
                 newFlags or PermissionFlags.SOFT_RESTRICTED
             } else {
                 newFlags andInv PermissionFlags.SOFT_RESTRICTED
@@ -1159,9 +1178,14 @@
                 }
             newFlags =
                 if (
-                    permission.isSoftRestricted && !isExempt &&
+                    permission.isSoftRestricted &&
+                        !isExempt &&
                         !requestingPackageStates.anyIndexed { _, it ->
-                            isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+                            isSoftRestrictedPermissionExemptForPackage(
+                                it,
+                                targetSdkVersion,
+                                permissionName
+                            )
                         }
                 ) {
                     newFlags or PermissionFlags.SOFT_RESTRICTED
@@ -1444,13 +1468,20 @@
     }
 
     // See also SoftRestrictedPermissionPolicy.mayGrantPermission()
+    // Note: we need the appIdTargetSdkVersion parameter here because we are OR-ing the exempt
+    // status for all packages in a shared UID, but the storage soft restriction logic needs to NOT
+    // exempt when the target SDK version is low, which is the opposite of what most of our code do,
+    // and thus can't check the individual package's target SDK version and rely on the OR among
+    // them.
     private fun isSoftRestrictedPermissionExemptForPackage(
         packageState: PackageState,
+        appIdTargetSdkVersion: Int,
         permissionName: String
     ): Boolean =
         when (permissionName) {
-            Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ->
-                packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE ->
+                appIdTargetSdkVersion >= Build.VERSION_CODES.Q
             else -> false
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
new file mode 100644
index 0000000..1f3184d
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display
+
+import android.os.IBinder
+import androidx.test.filters.SmallTest
+import com.android.server.display.brightness.clamper.HdrClamper
+import com.android.server.display.feature.DisplayManagerFlags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val MAX_BRIGHTNESS = 1.0f
+private const val TRANSITION_POINT = 0.7f
+private const val NORMAL_BRIGHTNESS_HIGH = 0.8f
+private const val NORMAL_BRIGHTNESS_LOW = 0.6f
+
+@SmallTest
+class BrightnessRangeControllerTest {
+
+    private val mockHbmController = mock<HighBrightnessModeController>()
+    private val mockCallback = mock<Runnable>()
+    private val mockConfig = mock<DisplayDeviceConfig>()
+    private val mockNormalBrightnessController = mock<NormalBrightnessModeController>()
+    private val mockHdrClamper = mock<HdrClamper>()
+    private val mockFlags = mock<DisplayManagerFlags>()
+    private val mockToken = mock<IBinder>()
+
+    @Test
+    fun `returns HBC max brightness if HBM supported and ON`() {
+        val controller = createController()
+        assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+    }
+
+    @Test
+    fun `returns NBC max brightness if device does not support HBM`() {
+        val controller = createController(hbmSupported = false)
+        assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+    }
+
+    @Test
+    fun `returns NBC max brightness if HBM not allowed`() {
+        val controller = createController(hbmAllowed = false)
+        assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+    }
+
+    @Test
+    fun `returns HBC max brightness if NBM is disabled`() {
+        val controller = createController(nbmEnabled = false, hbmAllowed = false)
+        assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+    }
+
+    @Test
+    fun `returns HBC max brightness if lower than NBC max brightness`() {
+        val controller = createController(
+            hbmAllowed = false,
+            hbmMaxBrightness = TRANSITION_POINT,
+            nbmMaxBrightness = NORMAL_BRIGHTNESS_HIGH
+        )
+        assertThat(controller.currentBrightnessMax).isEqualTo(TRANSITION_POINT)
+    }
+
+    private fun createController(
+        nbmEnabled: Boolean = true,
+        hbmSupported: Boolean = true,
+        hbmAllowed: Boolean = true,
+        hbmMaxBrightness: Float = MAX_BRIGHTNESS,
+        nbmMaxBrightness: Float = NORMAL_BRIGHTNESS_LOW
+    ): BrightnessRangeController {
+        whenever(mockFlags.isNbmControllerEnabled).thenReturn(nbmEnabled)
+        whenever(mockHbmController.deviceSupportsHbm()).thenReturn(hbmSupported)
+        whenever(mockHbmController.isHbmCurrentlyAllowed).thenReturn(hbmAllowed)
+        whenever(mockHbmController.currentBrightnessMax).thenReturn(hbmMaxBrightness)
+        whenever(mockNormalBrightnessController.currentBrightnessMax).thenReturn(nbmMaxBrightness)
+
+        return BrightnessRangeController(mockHbmController, mockCallback, mockConfig,
+            mockNormalBrightnessController, mockHdrClamper, mockFlags, mockToken,
+            DisplayDeviceInfo())
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b142334..48fc407 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -106,6 +106,7 @@
 import android.os.UserManager;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
+import android.test.mock.MockContentResolver;
 import android.util.SparseArray;
 import android.view.ContentRecordingSession;
 import android.view.Display;
@@ -123,6 +124,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -204,6 +207,8 @@
     @Rule
     public SetFlagsRule mSetFlagsRule =
             new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
     private Context mContext;
 
@@ -376,6 +381,8 @@
         when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class));
         mContext = spy(new ContextWrapper(
                 ApplicationProvider.getApplicationContext().createDisplayContext(display)));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
         mResources = Mockito.spy(mContext.getResources());
         mPowerHandler = new Handler(Looper.getMainLooper());
         manageDisplaysPermission(/* granted= */ false);
@@ -2408,6 +2415,7 @@
         when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
         DisplayManagerInternal localService = displayManager.new LocalService();
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2440,6 +2448,7 @@
                 .when(() -> SystemProperties.getBoolean(ENABLE_ON_CONNECT, false));
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
         DisplayManagerInternal localService = displayManager.new LocalService();
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2487,6 +2496,7 @@
         when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
@@ -2652,6 +2662,7 @@
         when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         DisplayManagerInternal localService = displayManager.new LocalService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2699,6 +2710,7 @@
         when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 76b7780..fb23213 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1927,6 +1927,8 @@
                 mock(ScreenOffBrightnessSensorController.class);
         final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
         final HdrClamper hdrClamper = mock(HdrClamper.class);
+        final NormalBrightnessModeController normalBrightnessModeController = mock(
+                NormalBrightnessModeController.class);
         BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
 
         when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
@@ -1939,7 +1941,8 @@
 
         TestInjector injector = spy(new TestInjector(displayPowerState, animator,
                 automaticBrightnessController, wakelockController, brightnessMappingStrategy,
-                hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
+                hysteresisLevels, screenOffBrightnessSensorController,
+                hbmController, normalBrightnessModeController, hdrClamper,
                 clamperController, mDisplayManagerFlagsMock));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
@@ -2027,6 +2030,8 @@
         private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
         private final HighBrightnessModeController mHighBrightnessModeController;
 
+        private final NormalBrightnessModeController mNormalBrightnessModeController;
+
         private final HdrClamper mHdrClamper;
 
         private final BrightnessClamperController mClamperController;
@@ -2040,6 +2045,7 @@
                 HysteresisLevels hysteresisLevels,
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController highBrightnessModeController,
+                NormalBrightnessModeController normalBrightnessModeController,
                 HdrClamper hdrClamper,
                 BrightnessClamperController clamperController,
                 DisplayManagerFlags flags) {
@@ -2051,6 +2057,7 @@
             mHysteresisLevels = hysteresisLevels;
             mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
             mHighBrightnessModeController = highBrightnessModeController;
+            mNormalBrightnessModeController = normalBrightnessModeController;
             mHdrClamper = hdrClamper;
             mClamperController = clamperController;
             mFlags = flags;
@@ -2163,7 +2170,8 @@
                 DisplayDeviceConfig displayDeviceConfig, Handler handler,
                 DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
             return new BrightnessRangeController(hbmController, modeChangeCallback,
-                    displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
+                    displayDeviceConfig, mNormalBrightnessModeController, mHdrClamper,
+                    mFlags, displayToken, info);
         }
 
         @Override
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 1529a08..1a71e77 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -228,13 +228,27 @@
 
     @Test
     public void testOnExternalDisplayAvailable() {
-        when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+
         mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+        assertNotAskedToEnableDisplay();
+        verify(mMockedExternalDisplayStatsService, never()).onDisplayConnected(any());
+
+        mExternalDisplayPolicy.onBootCompleted();
         assertAskedToEnableDisplay();
         verify(mMockedExternalDisplayStatsService).onDisplayConnected(eq(mMockedLogicalDisplay));
     }
 
     @Test
+    public void testOnExternalDisplayUnpluggedBeforeBootCompletes() {
+        mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+        mExternalDisplayPolicy.handleLogicalDisplayDisconnectedLocked(mMockedLogicalDisplay);
+        mExternalDisplayPolicy.onBootCompleted();
+        assertNotAskedToEnableDisplay();
+        verify(mMockedExternalDisplayStatsService, never()).onDisplayConnected(any());
+        verify(mMockedExternalDisplayStatsService, never()).onDisplayDisconnected(anyInt());
+    }
+
+    @Test
     public void testOnExternalDisplayAvailable_criticalThermalCondition()
             throws RemoteException {
         // Disallow external displays due to thermals.
@@ -303,8 +317,14 @@
                 mDisplayEventCaptor.capture());
         assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
         assertThat(mDisplayEventCaptor.getValue()).isEqualTo(EVENT_DISPLAY_CONNECTED);
+        verify(mMockedLogicalDisplay).setEnabledLocked(false);
         clearInvocations(mMockedLogicalDisplayMapper);
-        when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(true);
+        clearInvocations(mMockedLogicalDisplay);
+    }
+
+    private void assertNotAskedToEnableDisplay() {
+        verify(mMockedInjector, never()).sendExternalDisplayEventLocked(any(), anyInt());
+        verify(mMockedLogicalDisplay, never()).setEnabledLocked(anyBoolean());
     }
 
     private void assertIsExternalDisplayAllowed(final boolean enabled) {
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0089d4c..2724149 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -111,6 +111,8 @@
     <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
     <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.CAMERA" />
 
     <queries>
         <package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 7f88b00..eb89503 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -88,6 +88,7 @@
 
 import com.android.compatibility.common.util.TestUtils;
 import com.android.internal.R;
+import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.FloatingMenuSize;
 import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -1318,6 +1319,152 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
+        mTestableContext.getTestablePermissions().setPermission(
+                Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
+        mockManageAccessibilityGranted(mTestableContext);
+
+        assertThrows(SecurityException.class,
+                () -> mA11yms.notifyQuickSettingsTilesChanged(
+                        mA11yms.getCurrentUserState().mUserId,
+                        List.of(
+                                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
+        mockStatusBarServiceGranted(mTestableContext);
+        mTestableContext.getTestablePermissions().setPermission(
+                Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
+
+        assertThrows(SecurityException.class,
+                () -> mA11yms.notifyQuickSettingsTilesChanged(
+                        mA11yms.getCurrentUserState().mUserId,
+                        List.of(
+                                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
+        mockStatusBarServiceGranted(mTestableContext);
+        mockManageAccessibilityGranted(mTestableContext);
+        List<ComponentName> tiles = List.of(
+                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+                AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+        );
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                tiles
+        );
+
+        assertThat(
+                mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
+        ).containsExactlyElementsIn(tiles);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_sameQsTiles_noUpdateToA11yTilesInQsPanel() {
+        notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel();
+        List<ComponentName> tiles =
+                mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel().stream().toList();
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                tiles
+        );
+
+        assertThat(
+                mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
+        ).containsExactlyElementsIn(tiles);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
+        mockStatusBarServiceGranted(mTestableContext);
+        mockManageAccessibilityGranted(mTestableContext);
+        setupShortcutTargetServices();
+        ComponentName tile = new ComponentName(
+                TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+                TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                List.of(tile)
+        );
+
+        assertThat(mA11yms.getCurrentUserState().getA11yQsTargets()).doesNotContain(tile);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
+        mockStatusBarServiceGranted(mTestableContext);
+        mockManageAccessibilityGranted(mTestableContext);
+        setupShortcutTargetServices();
+        final AccessibilityUserState userState = mA11yms.getCurrentUserState();
+        userState.mAccessibilityButtonTargets.clear();
+        userState.mAccessibilityButtonTargets.add(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
+        ComponentName tile = new ComponentName(
+                TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+                TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                List.of(tile)
+        );
+
+        assertThat(mA11yms.getCurrentUserState().getA11yQsTargets())
+                .contains(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
+        mockStatusBarServiceGranted(mTestableContext);
+        mockManageAccessibilityGranted(mTestableContext);
+        List<ComponentName> tiles = List.of(
+                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+                AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+        );
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                tiles
+        );
+
+        assertThat(
+                mA11yms.getCurrentUserState().getA11yQsTargets()
+        ).containsExactlyElementsIn(List.of(
+                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
+                AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString())
+        );
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+    public void notifyQuickSettingsTilesChanged_removeFrameworkTile_qsShortcutDisabled() {
+        notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled();
+        Set<ComponentName> qsTiles = mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel();
+        qsTiles.remove(AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME);
+
+        mA11yms.notifyQuickSettingsTilesChanged(
+                mA11yms.getCurrentUserState().mUserId,
+                qsTiles.stream().toList()
+        );
+
+        assertThat(
+                mA11yms.getCurrentUserState().getA11yQsTargets()
+        ).doesNotContain(
+                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString());
+    }
+
     private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
             ComponentName componentName) {
         return mockAccessibilityServiceInfo(
@@ -1367,6 +1514,11 @@
                 PackageManager.PERMISSION_GRANTED);
     }
 
+    private void mockStatusBarServiceGranted(TestableContext context) {
+        context.getTestablePermissions().setPermission(Manifest.permission.STATUS_BAR_SERVICE,
+                PackageManager.PERMISSION_GRANTED);
+    }
+
     private void assertStartActivityWithExpectedComponentName(Context mockContext,
             String componentName) {
         verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 52a5d8f..b1964e2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -30,6 +30,8 @@
 
 import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
@@ -45,6 +47,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.platform.test.annotations.RequiresFlagsDisabled;
@@ -59,6 +63,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.internal.util.test.FakeSettingsProvider;
 
 import org.junit.After;
@@ -68,6 +73,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Map;
+import java.util.Set;
+
 /** Tests for AccessibilityUserState */
 public class AccessibilityUserStateTest {
 
@@ -431,7 +439,70 @@
 
         assertEquals(focusStrokeWidthValue, mUserState.getFocusStrokeWidthLocked());
         assertEquals(focusColorValue, mUserState.getFocusColorLocked());
+    }
 
+    @Test
+    public void updateA11yQsTargetLocked_valueUpdated() {
+        Set<String> newTargets = Set.of(
+                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
+                AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString()
+        );
+
+        mUserState.updateA11yQsTargetLocked(newTargets);
+
+        assertThat(mUserState.getA11yQsTargets()).isEqualTo(newTargets);
+    }
+
+    @Test
+    public void getA11yQsTargets_returnsCopiedData() {
+        updateA11yQsTargetLocked_valueUpdated();
+
+        Set<String> targets = mUserState.getA11yQsTargets();
+        targets.clear();
+
+        assertThat(mUserState.getA11yQsTargets()).isNotEmpty();
+    }
+
+    @Test
+    public void updateA11yTilesInQsPanelLocked_valueUpdated() {
+        Set<ComponentName> newTargets = Set.of(
+                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+                AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+        );
+
+        mUserState.updateA11yTilesInQsPanelLocked(newTargets);
+
+        assertThat(mUserState.getA11yQsTilesInQsPanel()).isEqualTo(newTargets);
+    }
+
+    @Test
+    public void getA11yQsTilesInQsPanel_returnsCopiedData() {
+        updateA11yTilesInQsPanelLocked_valueUpdated();
+
+        Set<ComponentName> targets = mUserState.getA11yQsTilesInQsPanel();
+        targets.clear();
+
+        assertThat(mUserState.getA11yQsTilesInQsPanel()).isNotEmpty();
+    }
+
+    @Test
+    public void getTileServiceToA11yServiceInfoMapLocked() {
+        final ComponentName tileComponent =
+                new ComponentName(COMPONENT_NAME.getPackageName(), "FakeTileService");
+        ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.packageName = tileComponent.getPackageName();
+        serviceInfo.name = COMPONENT_NAME.getClassName();
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = serviceInfo;
+        when(mMockServiceInfo.getTileServiceName()).thenReturn(tileComponent.getClassName());
+        when(mMockServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
+        mUserState.mInstalledServices.add(mMockServiceInfo);
+        mUserState.updateTileServiceMapForAccessibilityServiceLocked();
+
+        Map<ComponentName, AccessibilityServiceInfo> actual =
+                mUserState.getTileServiceToA11yServiceInfoMapLocked();
+
+        assertThat(actual).containsExactly(tileComponent, mMockServiceInfo);
     }
 
     private int getSecureIntForUser(String key, int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index dc26e6e..f6dc2f0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -18,21 +18,23 @@
 
 import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
 import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.windowId;
 import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.EventWindowIdMatcher.eventWindowId;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -350,6 +352,88 @@
     }
 
     @Test
+    public void onWindowsChanged_shouldNotReportNonTouchableWindow() {
+        final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+        when(window.isTouchable()).thenReturn(false);
+        final int windowId = mA11yWindowManager.findWindowIdLocked(
+                USER_SYSTEM_ID, window.getWindowInfo().token);
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        final List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        assertThat(a11yWindows, not(hasItem(windowId(windowId))));
+    }
+
+    @Test
+    public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() {
+        final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+                DEFAULT_FOCUSED_INDEX);
+        when(window.isTouchable()).thenReturn(false);
+        final int windowId = mA11yWindowManager.findWindowIdLocked(
+                USER_SYSTEM_ID, window.getWindowInfo().token);
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        final List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        assertThat(a11yWindows, hasItem(windowId(windowId)));
+    }
+
+    @Test
+    public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() {
+        // Make the focused trusted un-touchable window fullscreen.
+        final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+                DEFAULT_FOCUSED_INDEX);
+        setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+        when(window.isTouchable()).thenReturn(false);
+        when(window.isTrustedOverlay()).thenReturn(true);
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        final List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+    }
+
+    @Test
+    public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() {
+        // Make the a11y overlay window fullscreen.
+        final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+        setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+        when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        final List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+    }
+
+    @Test
+    public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() {
+        // Make the front window fullscreen.
+        final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+        setRegionForMockAccessibilityWindow(frontWindow,
+                new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+        final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+                USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+        final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(
+                DEFAULT_FOCUSED_INDEX);
+        final int focusedWindowId = mA11yWindowManager.findWindowIdLocked(
+                USER_SYSTEM_ID, focusedWindow.getWindowInfo().token);
+
+        onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+        final List<AccessibilityWindowInfo> a11yWindows =
+                mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+        assertThat(a11yWindows, hasSize(2));
+        assertThat(a11yWindows.get(0), windowId(frontWindowId));
+        assertThat(a11yWindows.get(1), windowId(focusedWindowId));
+    }
+
+    @Test
     public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
         assertNotEquals("new title",
                 toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
@@ -631,11 +715,11 @@
                 .sendAccessibilityEventForCurrentUserLocked(captor.capture());
         assertThat(captor.getAllValues().get(0),
                 allOf(displayId(Display.DEFAULT_DISPLAY),
-                        a11yWindowId(currentActiveWindowId),
+                        eventWindowId(currentActiveWindowId),
                         a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
         assertThat(captor.getAllValues().get(1),
                 allOf(displayId(Display.DEFAULT_DISPLAY),
-                        a11yWindowId(eventWindowId),
+                        eventWindowId(eventWindowId),
                         a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
     }
 
@@ -661,7 +745,7 @@
                 .sendAccessibilityEventForCurrentUserLocked(captor.capture());
         assertThat(captor.getAllValues().get(0),
                 allOf(displayId(Display.DEFAULT_DISPLAY),
-                        a11yWindowId(eventWindowId),
+                        eventWindowId(eventWindowId),
                         a11yWindowChanges(
                                 AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
     }
@@ -710,12 +794,12 @@
                 .sendAccessibilityEventForCurrentUserLocked(captor.capture());
         assertThat(captor.getAllValues().get(0),
                 allOf(displayId(initialDisplayId),
-                        a11yWindowId(initialWindowId),
+                        eventWindowId(initialWindowId),
                         a11yWindowChanges(
                                 AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
         assertThat(captor.getAllValues().get(1),
                 allOf(displayId(eventDisplayId),
-                        a11yWindowId(eventWindowId),
+                        eventWindowId(eventWindowId),
                         a11yWindowChanges(
                                 AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
     }
@@ -771,11 +855,11 @@
                 .sendAccessibilityEventForCurrentUserLocked(captor.capture());
         assertThat(captor.getAllValues().get(0),
                 allOf(displayId(Display.DEFAULT_DISPLAY),
-                        a11yWindowId(eventWindowId),
+                        eventWindowId(eventWindowId),
                         a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
         assertThat(captor.getAllValues().get(1),
                 allOf(displayId(Display.DEFAULT_DISPLAY),
-                        a11yWindowId(currentActiveWindowId),
+                        eventWindowId(currentActiveWindowId),
                         a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
     }
 
@@ -979,7 +1063,7 @@
                 .sendAccessibilityEventForCurrentUserLocked(captor.capture());
         assertThat(captor.getAllValues().get(0),
                 allOf(displayId(Display.DEFAULT_DISPLAY),
-                        a11yWindowId(windowId),
+                        eventWindowId(windowId),
                         a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
     }
 
@@ -1001,7 +1085,7 @@
                 .sendAccessibilityEventForCurrentUserLocked(captor.capture());
         assertThat(captor.getAllValues().get(0),
                 allOf(displayId(Display.DEFAULT_DISPLAY),
-                        a11yWindowId(windowId),
+                        eventWindowId(windowId),
                         a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
     }
 
@@ -1019,7 +1103,7 @@
                 .sendAccessibilityEventForCurrentUserLocked(captor.capture());
         assertThat(captor.getAllValues().get(0),
                 allOf(displayId(Display.DEFAULT_DISPLAY),
-                        a11yWindowId(windowId),
+                        eventWindowId(windowId),
                         a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
     }
 
@@ -1173,8 +1257,6 @@
 
     private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
         final WindowInfo windowInfo = WindowInfo.obtain();
-        // TODO(b/325341171): add tests with various kinds of windows such as
-        //  changing window types, touchable or not, trusted or not, etc.
         windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
         windowInfo.token = windowToken.asBinder();
 
@@ -1235,16 +1317,16 @@
         }
     }
 
-    static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+    static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
         private int mWindowId;
 
-        WindowIdMatcher(int windowId) {
+        EventWindowIdMatcher(int windowId) {
             super();
             mWindowId = windowId;
         }
 
-        static WindowIdMatcher a11yWindowId(int windowId) {
-            return new WindowIdMatcher(windowId);
+        static EventWindowIdMatcher eventWindowId(int windowId) {
+            return new EventWindowIdMatcher(windowId);
         }
 
         @Override
@@ -1280,4 +1362,27 @@
             description.appendText("Matching to window changes " + mWindowChanges);
         }
     }
+
+    static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> {
+        private final int mWindowId;
+
+        WindowIdMatcher(int windowId) {
+            super();
+            mWindowId = windowId;
+        }
+
+        static WindowIdMatcher windowId(int windowId) {
+            return new WindowIdMatcher(windowId);
+        }
+
+        @Override
+        protected boolean matchesSafely(AccessibilityWindowInfo window) {
+            return window.getId() == mWindowId;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("Matching to windowId " + mWindowId);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index 2890078..8a6ba4d 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -67,7 +67,6 @@
         // Start some ops
         appOpsManager.startOp(AppOpsManager.OP_FINE_LOCATION);
         appOpsManager.startOp(AppOpsManager.OP_CAMERA);
-        appOpsManager.startOp(AppOpsManager.OP_RECORD_AUDIO);
 
         // Verify that we got called for the ops being started
         final InOrder inOrder = inOrder(listener);
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 f29d215..99ab405 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -325,7 +325,6 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -13226,35 +13225,6 @@
     }
 
     @Test
-    public void fixNotification_customAllowlistToken()
-            throws Exception {
-        Notification n = new Notification.Builder(mContext, "test")
-                .build();
-        try {
-            Field allowlistToken = Class.forName("android.app.Notification").
-                    getDeclaredField("mAllowlistToken");
-            allowlistToken.setAccessible(true);
-            allowlistToken.set(n, new Binder());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-
-        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
-
-        IBinder actual = null;
-        try {
-            Field allowlistToken = Class.forName("android.app.Notification").
-                    getDeclaredField("mAllowlistToken");
-            allowlistToken.setAccessible(true);
-            actual = (IBinder) allowlistToken.get(n);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-
-        assertTrue(mService.ALLOWLIST_TOKEN == actual);
-    }
-
-    @Test
     public void testCancelAllNotifications_IgnoreUserInitiatedJob() throws Exception {
         when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
                 .thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 961fdfb..10cfb5f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -315,6 +315,23 @@
     }
 
     @Test
+    public void testUserLeaving() {
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task task = activity.getTask();
+        mSupervisor.mUserLeaving = true;
+        activity.setState(ActivityRecord.State.RESUMED, "test");
+        task.sleepIfPossible(false /* shuttingDown */);
+        verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+
+        clearInvocations(task);
+        activity.setState(ActivityRecord.State.RESUMED, "test");
+        task.setPausingActivity(null);
+        doReturn(false).when(task).canBeResumed(any());
+        task.pauseActivityIfNeeded(null /* resuming */, "test");
+        verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+    }
+
+    @Test
     public void testSwitchUser() {
         final Task rootTask = createTask(mDisplayContent);
         final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -1823,6 +1840,66 @@
     }
 
     @Test
+    public void testAssignChildLayers_boostedDecorSurfacePlacement() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final Task task =  new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+        final ActivityRecord unembeddedActivity = task.getTopMostActivity();
+
+        final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final SurfaceControl.Transaction t = task.getSyncTransaction();
+        final SurfaceControl.Transaction clientTransaction = mock(SurfaceControl.Transaction.class);
+
+        doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+        spyOn(unembeddedActivity);
+        spyOn(fragment1);
+        spyOn(fragment2);
+
+        doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid);
+        doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
+        doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
+        doReturn(true).when(fragment1).isVisible();
+
+        task.moveOrCreateDecorSurfaceFor(fragment1);
+
+        clearInvocations(t);
+        clearInvocations(unembeddedActivity);
+        clearInvocations(fragment1);
+        clearInvocations(fragment2);
+
+        // The decor surface should be placed above all the windows when boosted and the cover
+        // surface should show.
+        task.setDecorSurfaceBoosted(fragment1, true /* isBoosted */, clientTransaction);
+
+        verify(unembeddedActivity).assignLayer(t, 0);
+        verify(fragment1).assignLayer(t, 1);
+        verify(fragment2).assignLayer(t, 2);
+        verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 3);
+
+        verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+        verify(t).merge(clientTransaction);
+
+        clearInvocations(t);
+        clearInvocations(unembeddedActivity);
+        clearInvocations(fragment1);
+        clearInvocations(fragment2);
+
+        // The decor surface should be placed just above the owner TaskFragment and the cover
+        // surface should hide.
+        task.moveOrCreateDecorSurfaceFor(fragment1);
+        task.setDecorSurfaceBoosted(fragment1, false /* isBoosted */, clientTransaction);
+
+        verify(unembeddedActivity).assignLayer(t, 0);
+        verify(fragment1).assignLayer(t, 1);
+        verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2);
+        verify(fragment2).assignLayer(t, 3);
+
+        verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+        verify(t).merge(clientTransaction);
+
+    }
+
+    @Test
     public void testMoveTaskFragmentsToBottomIfNeeded() {
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d3a50bb..df32fbd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3315,6 +3315,18 @@
             "support_no_reply_timer_for_cfnry_bool";
 
     /**
+     * No reply time value to be sent to network for call forwarding on no reply
+     * (CFNRy 3GPP TS 24.082 version 17.0 section 3).
+     * Controls time in seconds for the no reply condition on in the call forwarding
+     * settings UI.
+     * This is available when {@link #KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL} is true.
+     *
+     * @hide
+     */
+    public static final String KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT =
+            "no_reply_timer_for_cfnry_sec_int";
+
+    /**
      * List of the FAC (feature access codes) to dial as a normal call.
      * @hide
      */
@@ -10666,6 +10678,7 @@
         sDefaults.putBoolean(KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true);
+        sDefaults.putInt(KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT, 20);
         sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_PRECISE_FAILED_CAUSE_BOOL, false);
diff --git a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
index 443de8e..7cdab94 100644
--- a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
index c51da05..377288d 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
index ab23401..68b1473 100644
--- a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
Binary files differ